Security¶
enconf provides multiple security layers to protect the panel and hosted websites.
Customer isolation (defence in depth)¶
enconf isolates every customer across several independent layers. Worth being clear about: 100% isolation on a single-kernel system is physically impossible — that takes containers or VMs. What we build is defence in depth: each layer has to be defeated separately for a cross-tenant attack to succeed.
Active and rolled out automatically¶
| Layer | What it protects |
|---|---|
| A Linux user per customer | Standard Unix DAC. Files belong to the customer and are not readable by others. |
Webroot mode 0710 user:www-data |
The owner may do everything, nginx (www-data) may traverse, every other customer has no access — they cannot even cd into the directory. |
| A PHP-FPM pool per customer | Its own user and group — PHP runs as the customer, not as a shared www-data process. |
open_basedir |
PHP requests can only read and write within <docroot>:<tmpDir>:/usr/share/php:/var/cache/php. |
disable_functions |
exec, passthru, shell_exec, system, popen, proc_open, dl and every pcntl_* are disabled — no shell access through PHP. |
Per-customer /tmp/<user> mode 0700 |
Its own path for session.save_path, upload_tmp_dir and sys_temp_dir. No cross-tenant session or upload leak through a shared /tmp or /var/lib/php/sessions/. |
| MariaDB grants per customer | GRANT … ON customer_db.* strictly — no user holds global rights, cross-database queries are blocked. |
Panel secrets 0600 root:root |
JWT_SECRET, DB_PASSWORD and AGENT_TOKEN are not readable by customer PHP pools. |
ProFTPD DefaultRoot ~ |
FTP and SFTP users are chrooted into their own home and cannot navigate to other customers or system paths. |
| Nginx upload path block | .php inside upload directories is not executed — this closes the most common WordPress attack pattern. |
| HTTPS catch-all (incl. HTTP/3) | A name no vhost serves gets a neutral, self-signed certificate and a 404 response — never a customer's certificate. This also holds for HTTP/3 (QUIC); there the server used to hand out the certificate of the first customer in the configuration. |
| nftables SMTP egress block | Customer PHP cannot connect directly to foreign SMTP servers, which blocks spam. Authenticated submission on 587 stays open. |
| FFI disabled | ffi.enable=0 closes the FFI::cdef route to libc that would otherwise sidestep disable_functions. |
| An AppArmor profile per customer | Its own profile per PHP-FPM, switchable between complain and enforce mode per server, with automatic fallback when false positives pile up. |
| A systemd slice per customer | MemoryMax, CPUQuota, TasksMax and IO limits — applied to new and existing sites. |
| Kernel hardening (sysctl) | kptr_restrict, dmesg_restrict, ptrace_scope, unprivileged_bpf_disabled and others. |
| fail2ban | Brute-force protection for SSH, FTP, mail and the panel login. |
| Per-customer nginx logs | Logs live under /var/www/customers/<user>/logs/ — no cross-tenant log reading. |
| Hardening migration on agent start | Existing sites are brought up to the current hardening state automatically on update, idempotently. |
Demonstrability (active)¶
| Layer | Status |
|---|---|
| Per-customer security score | Every customer sees live in the dashboard which isolation layers are actually active per website. |
| Security Advisor | Server-wide audit checklist, SBOM, live CVE feed, evidence and export. |
| SIEM log forwarding | Security logs shipped fleet-wide to a collector over TLS. |
In depth: see the security architecture whitepaper.
Planned (phase 2)¶
| Layer | Status |
|---|---|
| AppArmor enforce fleet-wide | The per-server enforce toggle is built; the broad rollout waits for enough complain-mode observation data. |
| True kernel isolation as the default | Rootless containers (gVisor) are available opt-in; not yet the default for untrusted workloads. |
What fundamentally is not isolated¶
These points apply to every single-kernel, single-MariaDB system — Plesk, cPanel and CyberPanel included — and cannot be fixed by a panel layer:
- The Linux kernel — every customer shares one. Mitigation: apply kernel updates promptly.
- The MariaDB process — one instance for everyone. Mitigation: updates.
- The PHP binary — all pools on the same PHP version share the binary code.
- Postfix, Dovecot and PowerDNS — one instance for all domains.
Two-Factor Authentication (2FA)¶
2FA protects panel access through an additional authentication factor beyond the password.
Enable 2FA¶
- Navigate to Settings > Security (or Settings > Password)
- Click Set Up 2FA
- Scan the QR code with an authenticator app:
- Google Authenticator
- Authy
- Microsoft Authenticator
- FreeOTP
- Enter the displayed 6-digit code
- Click Confirm
Backup Codes
Note the secret key displayed below the QR code. You can use it to restore access if you lose your device.
Disable 2FA¶
- Navigate to Settings > Security
- Click Disable 2FA
- Enter your current password for confirmation
- Click Disable
Login with 2FA¶
- Enter email and password
- Enter the 6-digit code from your authenticator app
- Click Sign In
TOTP Protocol
WebPanel uses the TOTP protocol (Time-based One-Time Password) as per RFC 6238.
Enforce 2FA Policy¶
As an administrator, you can require specific user groups to set up two-factor authentication.
Configuration¶
- Navigate to Settings > Security
- Select the desired policy under Enforce 2FA:
| Option | Description |
|---|---|
| Disabled | 2FA is optional for all users |
| Admins | Admins must set up 2FA |
| Admins & Resellers | Admins and resellers must set up 2FA |
| All Users | All users (including customers) must set up 2FA |
- Click Save
Behavior for Affected Users¶
Users subject to the policy who have not yet set up 2FA:
- See a yellow warning banner in the panel after login
- Can continue to use the panel normally
- Are prompted to set up 2FA under Settings
Recommendation
Enable the policy at minimum for admins to protect panel accounts with elevated privileges.
Brute-Force Protection¶
The built-in brute-force protection locks IP addresses after too many failed login attempts.
Configuration¶
Navigate to Settings > Security > Login Protection:
| Setting | Default | Description |
|---|---|---|
| Enabled | Yes | Enable/disable brute-force protection |
| Max. Failed Attempts | 5 | Number of allowed attempts |
| Lockout Duration (Minutes) | 15 | Duration of the lockout |
Login protection IP whitelist¶
IPs that are never locked out:
- Navigate to Settings > Security > Login Protection
- Enter trusted IP addresses
- These IPs will not be locked out even after failed attempts
Whitelist Your Own IP
Add your own static IP address to the whitelist to prevent accidentally locking yourself out.
Not the same as IP Access Restriction
This list only decides who is never locked out after failed logins. It grants no access and denies none. The feature that restricts who may reach the panel at all is a different one — see below.
IP Access Restriction (panel access)¶
Restricts access to the admin panel to selected IP addresses. Unlike the login protection whitelist, this is a gate: anything not on the list is refused.
- Navigate to Settings > Security
- Enable IP whitelist enabled
- Under Allowed IP Addresses, enter one address per line
Single addresses and CIDR ranges are accepted, IPv4 as well as IPv6:
What the restriction covers¶
It applies to every administrative request and is evaluated before you are identified. If your address is not on the list, it is not only saving that fails but loading as well: the dashboard reads zero everywhere, settings pages look empty, and the restriction itself appears to be switched off. Nothing is lost — the panel is simply refusing every request, including the one that would show you the current state.
Entering the right address¶
The form shows you the address the server actually sees you coming from. Use that one, not the answer from a "what is my IP" service. The two answer different questions and routinely differ — for instance when your browser reaches the panel over IPv6 while that lookup went out over IPv4, or when a router or provider proxy sits in between.
Saving is refused if it would lock you out
Since version 2.9.83 the panel checks whether your own address is covered by the list you are saving. If it is not, nothing is saved and the message names the address to add instead. Locking yourself out through the interface is no longer possible.
Access from the server itself always stays open
Requests from 127.0.0.1 and ::1 are always permitted, whatever the list
says. That is the way back in should the restriction ever become too narrow —
for example because your address changed.
Undoing a lockout¶
Connect to the server via SSH as root. The log shows which address the panel
actually sees:
Switch the restriction off:
DB=$(grep -oP '^DB_NAME=\K.*' /etc/enconf/api.env | tr -d '"')
sudo -u postgres psql -d "$DB" \
-c "UPDATE panel_settings SET value='false' WHERE key='panel_ip_whitelist_enabled';"
systemctl restart enconf-api
The panel is then fully reachable again and every setting is exactly as you left it.
Changes are not instant
The list is cached for up to 60 seconds. Without restarting the service a change can therefore take a moment to take effect.
Password Policy¶
The password policy applies to all users (admin, reseller, customers):
| Setting | Default | Description |
|---|---|---|
| Minimum Length | 12 | Minimum number of characters |
| Uppercase | No | At least one uppercase letter |
| Lowercase | No | At least one lowercase letter |
| Digits | No | At least one digit |
| Special Characters | No | At least one special character |
Why no character classes are enforced
The default follows NIST 800-63B: length protects better than enforced
character classes. A long passphrase such as
correct-horse-battery-staple is stronger than a short P4ssw!rd — yet a
special-character rule would reject it.
You can switch the character classes on at any time if a policy in your organisation requires it. Bear in mind that doing so also rules out strong passphrases.
Configuration¶
- Navigate to Settings > Security > Password Policy
- Adjust the desired rules
- Click Save
Audit Log¶
The audit log records all security-relevant actions in the panel.
Logged Events¶
| Event | Description |
|---|---|
| Login | Successful and failed logins |
| Logout | Logouts |
| Password Change | Password changes for all users |
| 2FA Change | Activation/deactivation of 2FA |
| Impersonation | Admin logs in as customer/reseller |
| Customer Created/Deleted | Customer management actions |
| Subscription Created/Deleted | Subscription actions |
| Settings Changed | Changes to panel settings |
| API Key Created/Deleted | API key management |
| Firewall Rule Changed | Firewall changes |
View Audit Log¶
- Navigate to Audit Log
- The table shows all logged events:
| Column | Description |
|---|---|
| Timestamp | Date and time |
| User | Executing user |
| Action | Type of action |
| Details | Additional information |
| IP Address | Source IP of the user |
Filters¶
- Time Range — Filter by date
- User — Filter by user
- Action — Filter by action type
Security Advisor¶
The Security Advisor checks the security configuration and provides recommendations:
Checked Areas¶
| Area | Check |
|---|---|
| Firewall | Is the firewall active? |
| fail2ban | Is fail2ban active and configured? |
| 2FA | Is 2FA enabled for the admin? |
| SSL | Do all domains have valid SSL certificates? |
| Password Policy | Is the policy sufficiently strong? |
| PHP Versions | Are outdated PHP versions in use? |
| Updates | Are system updates available? |
| SSH | Is the SSH configuration secure? |
| open_basedir | Configured for all sites? |
| disable_functions | Dangerous functions disabled? |
Ratings¶
Each check receives a rating:
- Passed (Green) — Securely configured
- Warning (Yellow) — Improvement recommended
- Critical (Red) — Immediate action required
Customer Isolation¶
Every hosting account is fully isolated:
Linux Level¶
- Dedicated Linux user per subscription
- PHP-FPM pool runs under the customer's user
- open_basedir restricts PHP to the customer's directory
- disable_functions disables dangerous PHP functions
- systemd slice limits CPU, RAM and processes
Network Level¶
- nftables blocks outgoing SMTP connections from customer processes
- Customers cannot send emails directly (only through the mail server)
Web Server Level¶
- Nginx blocks PHP execution in upload directories
- Each site has its own vhost configuration
- Access restrictions per directory are possible
SSH Hardening¶
Recommended SSH settings (in /etc/ssh/sshd_config):
| Setting | Recommendation | Description |
|---|---|---|
| PermitRootLogin | no |
Disable root login |
| PasswordAuthentication | no |
Key-based authentication only |
| Port | Non-standard | SSH on a port other than 22 |
| MaxAuthTries | 3 |
Max. login attempts |
| AllowUsers | Specific | Allow only specific users |
Ensure Access
Before making changes to the SSH configuration, ensure you have alternative access methods (e.g. console at your hosting provider).
Login anomaly detection¶
On top of the IP-based brute-force lockout (fail2ban + account lockout after too many failed attempts), the panel watches sign-in patterns and alerts on two suspicious signals:
- Sign-in from an unknown network: when an admin or reseller account signs in from a network (IPv4
/24, IPv6/48) it has never used before, a notification is raised. An account's very first login establishes its baseline and deliberately does not alert. - Distributed login attack: when a single account is hit with many failed logins from several IPs within 30 minutes, the panel reports it — even when no single IP reaches the lockout threshold (the "low-and-slow" attack the per-IP lockout misses).
Both signals are alert-only (no automatic lockout) and enabled out of the box. Disable via the panel setting login_anomaly_enabled=false; tune the distributed-attack threshold via login_anomaly_account_threshold (default 15).
Restoring administrator access¶
If you are locked out of the panel — forgotten password, a login address mistyped while changing it, or the suspicion that someone else has taken over the account — restore access on the server. A command-line tool does this; it runs only as root (over SSH, with sudo) and therefore needs neither a password nor a session:
sudo enconfctl admin list # admin accounts, login address, 2FA
sudo enconfctl admin reset-password <address> [-p <password>]
sudo enconfctl admin set-email <old-address> <new-address> [--sync-admin-email]
reset-passwordsets a new password (without-pone is generated and shown) and ends all existing sessions of that account. The tool deliberately bypasses the password policy.set-emailchanges the login address. Like the panel it checks that no other address is the same (ignoring upper/lower case) and leaves 2FA, password and sessions untouched. The operator contact address (admin_email, also the Let's Encrypt and MTA-STS contact) follows the login address only if the two matched before oradmin_emailwas empty; if it differs it is left alone and the tool prints the command with--sync-admin-emailin case it should follow.
Suspected takeover (for example after the notice "Your sign-in address was changed" that you did not trigger): first set-email back to your address, then reset-password, then revoke unknown sessions under Security → Sessions and check 2FA. "Forgot password" in the panel does not help here: the link goes to the stored login address, which is the new one.
Repeated wrong passwords when changing the password, the login address or 2FA lock that check for the account (default: 5 failed attempts in 15 minutes, the same limits as the login, see Brute-Force Protection). The lock then applies to the correct password too, until the time has passed.