About WPHoster
A WordPress hosting control panel for agencies and small hosts, on their own server.
How it started
I've worked in web hosting for more than 12 years. Hosting clients' WordPress sites always meant choosing between two compromises: a commercial panel that bills you for every account, or a cheaper setup where one hacked site can reach the files and databases of the others.
WPHoster began about 8 years ago as a private API I used to manage my own hosting servers. I wrote it first in C++, then rewrote it in Go. This is its third version: rewritten in Rust and turned into a full control panel that anyone can install.
It ships as a single binary that sets up the whole server (web server, PHP, databases, DNS, mail and backups) and isolates every client account from the others.
Why it's free
WPHoster is free, with no limits on accounts, sites or mailboxes.
I build and run it myself on a few servers, so costs stay low, and more servers running WPHoster means more feedback and a better panel. If paid extras ever arrive, such as priority support, they’ll sit on top. What’s free now stays free.
How client accounts are isolated
Every client account is treated as hostile. WPHoster is built so that a hacked WordPress site can damage its own account and nothing else, using standard Linux isolation: systemd sandboxing, control groups and file permissions.
What runs on the server
Every service handles traffic as an unprivileged user of its own. The jobs that need root (the agent, systemd, certificates, bans, statistics and backups) can’t be reached from the internet.
Panel
- WPHoster panel (with SQLite)
- wphoster, unprivileged
- Every account, but only through what the panel can ask for. No shell, no root commands.
- WPHoster agent
- root
- The whole server. It isn’t on the network: only the panel can reach its local socket, and it checks every request again.
- systemd
- root
- The whole server. Not reachable from the network; it builds the cages.
Web
- nginx
- www-data
- The files in every site’s web folder, which it serves anyway. Not wp-config.php (owner-only), databases or mail.
- PHP-FPM
- each account, in its cage
- Only that account.
- WP-CLI
- each account, in its cage
- Only that account.
- Redis
- each account, in its cage, no network
- Only that account’s cache.
- Certbot
- root, from a timer
- Nothing to attack from outside: it only connects out to Let’s Encrypt.
Databases
- MariaDB
- mysql
- Every database. Closed to the outside unless you open Remote MySQL to listed hosts.
- phpMyAdmin
- each account, in its cage
- Only that account’s databases. Reachable only through the panel.
DNS and FTP
- PowerDNS
- pdns
- Every hosted DNS zone. Not files, sites or databases.
- Pure-FTPd
- each account, after sign-in
- A signed-in session stays locked in its own home. TLS is required and repeated failures are banned.
- Postfix
- postfix
- Mail passing through the server. Not stored mailboxes.
- Dovecot
- each mailbox’s account
- A signed-in session reaches only its own account’s mailboxes.
- Rspamd
- _rspamd
- Spam scores and DKIM signing. Not stored mailboxes.
- ClamAV
- clamav
- The attachments it scans. Not stored mailboxes.
- Roundcube
- wphwebmail, its own PHP
- Webmail sessions. Mailboxes still need their own sign-in.
- Radicale
- radicale, local only
- Calendars and contacts.
- mlmmj
- mlmmj
- Mailing lists and their subscribers.
Security
- CrowdSec
- root
- Bans and firewall rules. It reads logs and listens only locally.
- nftables
- the kernel
- No service to attack: it is the firewall itself.
Statistics and backups
- AWStats
- root, sandboxed: read-only system, no home folders, no network
- Only the visitor statistics.
- restic
- root, from a timer
- Reads everything to back it up, and only connects out. The backups are encrypted.
Well-known open-source software from your distribution and a few vendor repositories. See the technology stack for what each one does.
What a new account gets
- A Linux user with no shell. SSH refuses every hosting account, whatever its password.
- A home folder only the account can enter. The web server may read its public_html, other accounts nothing.
- Its own PHP-FPM process, started inside the sandbox below, on its own socket.
- Its own MariaDB user, which signs in by its Linux user, so there is no database password to steal.
- One password, kept in the system’s shadow file, for the panel and FTP.
- A DNS zone for the main domain, if the server serves DNS.
If any step fails, everything made so far is removed again. See Create an Account.
Inside each account’s sandbox
Each account’s PHP and Redis run in a systemd sandbox. WordPress, its plugins and anything a visitor manages to upload run in it too.
Files
The operating system is read-only. Other homes, logs, mail and the panel’s data are hidden; the account sees its own home, a private /tmp and a minimal /etc whose user list names only itself.
Processes and kernel
Other processes are invisible. Kernel settings, modules, logs, the clock and control groups are out of reach.
Privileges
No setuid programs, no new privileges, no namespaces, and a system call filter limited to what a service needs.
Network
Sites may connect out (updates, APIs) but may not listen on a port, and the cloud provider’s metadata address, where server credentials can leak, is blocked.
PHP
open_basedir keeps PHP inside the home, and the functions that run shell commands are switched off.
Limits
Each account has its own control group with the package’s memory, CPU, process and disk I/O caps, plus a disk quota on ext4.
The limits come from the account’s package.
Kept apart in every service
- Web
- nginx follows a symlink only when its owner owns the target, keeps a log per site, and never runs PHP from uploads.
- Databases
- An account is granted only its own databases, named after it, and MariaDB is closed to the outside unless you open it to listed hosts.
- FTP
- TLS is required and each account is locked in its home; extra FTP logins are locked in one folder of it.
- Mail from PHP goes out under the account’s own login, counted against its hourly cap and DKIM-signed.
- Redis
- Each account’s cache runs in its own cage, with no network, on a socket in its home.
The panel and its releases
The web panel never runs as root
It runs as an unprivileged user. A separate agent does the root work and checks every request again before acting on it.
Brute-force protection
CrowdSec bans addresses that keep failing to sign in to the panel, FTP, mail, SSH or WordPress.
Signed updates that roll back
Releases are signed and install every night. If one fails, the server restores the previous version on its own.
Checked before every release
Static analysis of the code and its dependencies, a real install scanned from outside, and the end-to-end suite with its tenant-isolation checks.
See the latest release's results, read the install script before you run it, and follow the security best practices.