Panel security

The panel is reachable from the internet on port 1500, so its own sign-in page is protected in several layers.

Who can sign in

The normal way in is the panel sign-in: the name and password set during installation, stored as PANEL_USER and PANEL_PASSWORD in .env. Both can be changed in Settings > Panel access.

With ROOT_LOGIN on, root can also sign in with the server’s root password. The option is off by default. Only a session signed in as root gets the root terminal.

Sign-in for other users of the server with their OS password, each seeing only their own sites, is planned for 2.2 (see users and the roadmap).

How the root password is checked

The panel runs as the unprivileged user wardpane and cannot read /etc/shadow. A small root helper checks the root password with unix_chkpwd, the same tool PAM uses (it understands yescrypt). systemd starts the helper on the first connection to its socket, which only root and the panel user can open. The password is never written anywhere or logged.

On top of the panel’s limits, the helper has its own. It checks one password at a time and answers no sooner than after one second. After 5 wrong passwords in a minute it refuses for a minute, and each repeat doubles the pause, up to an hour. This state survives restarts, so even a compromised panel cannot guess passwords any faster.

Brute-force limits

  • 5 failed attempts (LOGIN_MAX_ATTEMPTS) block the address for 15 minutes (LOGIN_BLOCK_MINUTES). For IPv6 the whole /64 is blocked, since one subscriber usually has a whole /64.
  • 50 failures from one IPv6 /48 block the whole /48.
  • 50 failures from all addresses within a minute close sign-in for 5 minutes to every address that has not signed in before. One network contributes at most 5 failures a minute to this count. If the attack repeats soon after, the lock doubles, up to 60 minutes.
  • Addresses that signed in successfully in the last 30 days (up to 50 of them) are not affected by the global lock and the /48 block.
  • For root passwords there is an extra counter: after 10 wrong root passwords in an hour, root sign-in is accepted only from addresses that signed in before, even when an allow list is set.

To lift all sign-in blocks at once:

sudo node /opt/wardpane/bin/unlock-login.js

The IP allow list

With PANEL_ALLOW_IPS set (addresses and networks separated by commas, spaces or new lines), the panel does not answer any other address, not even with the sign-in page. If the variable is set but has no valid entries, the panel accepts connections only from the server itself. You can still reach it through an SSH tunnel:

ssh -L 1500:127.0.0.1:1500 root@your-server
# then open https://127.0.0.1:1500

Sessions

A session lasts at most SESSION_HOURS (12 hours) from sign-in and ends earlier after SESSION_IDLE_MINUTES (2 hours) without activity. The background refresh of an open page does not count as activity. If a session ends while you are editing a site, the unsaved changes are kept, and the form opens again after you sign in. API answers are sent with Cache-Control: no-store.

The root helpers

The panel never runs as root. When something needs root (creating Linux users, site folders, php-fpm pools, PM2 daemons, systemd units), the panel writes the desired state to a file, and a root helper started by systemd applies it. The helper checks every value again and trusts the panel no more than any other input. If a site’s settings fail that check, the site keeps running with its previous configuration and the panel shows the error.

Sandboxed sites

Every PHP site runs its own php-fpm, and every Node.js app its own PM2 process tree, as a separate Linux user in a systemd sandbox. Inside the sandbox the system is read-only, other sites and the panel’s folders cannot be seen, no extra privileges can be gained, and memory and CPU are limited. See PHP and Node.js.

Release checksums and safe updates

Release archives are published with their SHA-256 checksum. The installer downloads the archive over HTTPS and checks the SHA-256 before it runs anything. Every update takes a backup first and rolls back by itself if the new version does not start. See updating.