Secure SSH for hosting accounts, without CageFS
How CorePanel gives hosting accounts bash, rsync, git and wp-cli over SSH without showing them the server: a per-session jail, systemd limits and SELinux.
Sooner or later every customer on a hosting server asks for SSH. They want to deploy
with rsync, pull with git, update WordPress with wp-cli and run composer
without uploading a vendor folder over FTP. A plain login gives them all of that, along
with a full view of the server: every installed tool, every readable file, the process
list, and as many resources as they care to take.
There have been two answers so far. cPanel’s jailed shell hides the server behind bind
mounts under /home/virtfs, and caps a shell with Shell Fork Bomb Protection, a WHM
switch that applies a set of ulimit values with no CPU limit at all. CloudLinux adds
CageFS and LVE limits, as a separate operating system with its own licence. CorePanel
has a third answer. Every account can get a real shell, jailed and capped, on a
stock RHEL, AlmaLinux or Rocky server, in every edition, Personal included.
Four levels, one port
Each account has an SSH access level, set from its SSH access section or from the hosting package:
- Off: no SSH at all.
- SFTP only: file transfer, jailed to the account’s home. This is where new accounts start, so a customer can upload files on day one without ever getting a shell.
- Shell:
bashinside a jail. - Legacy: an account that already had a shell before CorePanel managed SSH, usually one migrated from cPanel. It moves to Shell in one click, keys included.
Everything runs on the SSH port you already use. Administrators and hosting accounts are
told apart by group membership in Match blocks, not by a second port, so there is no new
firewall rule and nothing new to reach. root and your own users are never matched: how you
log in does not change.
What the customer finds inside
At each login, pam_namespace builds a mount namespace for that session alone. Inside it,
the account finds:
- Its home, at its real path,
/home/<user>, so scripts and configs that hard-code the path keep working. - A
/tmpof its own, which no other account sees. - The server’s allowlisted tools, read-only: the file and text utilities,
tarand the compressors,viandnano,curl,git,rsync,sshandscp, the MariaDB client, every installed PHP withwp-cli, andcomposer,screenortmuxwhen the server has them.
What it does not find matters more. There is no other account, no server configuration
and no setuid program: /etc holds only what a shell needs, and sudo, su, passwd
and pkexec are not there to try. When customers need a program the jail does not
carry, you add it under Settings → Shell jail utilities, and it goes through the same
checks as the built-in set.
No jail left mounted in /home
This is where the design differs most from what hosting providers are used to.
cPanel builds its jailed shell, VirtFS, out of bind mounts under /home/virtfs/<user>:
/usr/bin, the libraries, the user’s own home, all linked into one tree that stays
there. cPanel’s own documentation warns that running rm on anything mounted under /home/virtfs also deletes the files it
points to, and can leave the server nonfunctional. It also notes that du may report disk
usage that isn’t real. cPanel even drops a 0_README_BEFORE_DELETING_VIRTFS file in the
folder, because every administrator who finds a large, strange directory under /home
wants to clean it up. Unmounting those trees is a job of its own, with a dedicated script,
clear_orphaned_virtfs_mounts.
CorePanel’s shell jail exists only inside the session’s mount namespace. Nothing else on
the server sees it, not even root from outside, and when the session ends the namespace
goes with it and leaves no mount on the host. Between sessions, /home holds homes and
nothing else: no phantom trees, no orphaned mounts, nothing for a cleanup script or a
backup tool to trip over. The home itself stays 0700 and owned by the customer. The
jail does not need it handed over to root to work.
Limits that hold, even against a fork bomb
A shell is also a way to start processes, so every shell account runs inside its own systemd slice with limits taken from its hosting package:
- Memory and CPU.
- Tasks, the number of processes and threads.
- Open files and maximum file size.
- Concurrent logins.
A new package caps tasks at 256. A fork bomb run in a customer’s shell hits that cap and
stops there, while the server and every other site on it keep answering. A ulimit
caps memory one process at a time; the slice caps the account as a whole, so memory, CPU
and tasks are one budget for every session it has open at once. Change a
package’s limits and new accounts take them. Change one account’s limits from its
Shell resources card, which the customer sees read-only in their own panel.
CorePanel’s values win over the server’s limits.d and systemctl set-property, so
nothing has to be tuned in two places.
SELinux does the part a chroot can’t
A filesystem jail decides what a process can see. It does not decide what a process can
do, so CorePanel does not stop there. With SELinux enforcing, the RHEL default, every
shell session runs confined as user_u. It cannot step into an administrative domain,
and it cannot reach the account’s own processes running outside the jail, such as its
PHP workers.
We don’t ask you to turn SELinux off to make hosting work. We use it as one of the walls.
Keys nobody can plant
Most hosting servers read ~/.ssh/authorized_keys from the customer’s home. That means
any code that can write to the home, such as a compromised WordPress plugin, can add an
attacker’s key and keep coming back. CorePanel keeps the key files outside the home,
owned by root and managed from the panel, so a compromised site cannot add its own.
The rest follows the same idea:
- Keys by default. Password login is something you grant per account, with an optional expiry, never something the customer turns on for themselves.
- No tunnels. Local, remote and dynamic port forwarding, agent forwarding and X11 are all denied, so a shell account cannot become a proxy into your network.
- Brute-force protection on SSH only. An IP guessing passwords is banned from the SSH port and nothing else, so nobody locks themselves out of the panel they would use to lift the ban.
- Suspension means suspension. Suspending an account closes its open SSH sessions, not just new logins.
How it compares to CloudLinux
CloudLinux’s CageFS is the best-known answer to this problem, and it has been around for years. It is a per-user virtualized filesystem, with resource limits from LVE. It is also a separate operating system with its own kernel module and its own licence.
| CloudLinux (CageFS + LVE) | CorePanel Shell | |
|---|---|---|
| Operating system | CloudLinux OS | Stock RHEL, AlmaLinux or Rocky |
| Kernel | The kmod-lve module | The distribution’s kernel, unchanged |
| Confinement | CageFS + LVE | Mount namespace + systemd slices + SELinux |
| SELinux | Supported from version 8, though its own docs warn it “might not work or be compatible with control panels and various components” | Enforcing, and part of the confinement |
| Price | $18 per server per month for shared hosting (CloudLinux OS Unlimited), on top of the panel | Included in every CorePanel edition |
For the customer, the result is the same: a shell that sees their own files and nothing else, and can’t take the server down. We get there with parts the operating system already has and maintains (mount namespaces, systemd and SELinux), so there is no second licence, no extra kernel module, and nothing to remove if you change your mind. PHP gets the same treatment from a different angle, through 0700 homes and a PHP-FPM pool per account; True user isolation, explained covers that half.
What it doesn’t do
- No port forwarding, by design. That rules out VS Code’s Remote-SSH, which needs
it.
rsync,scp,gitover SSH and interactive sessions all work. - No long-running services.
systemctl --userand anything meant to outlive the session don’t run. Applications belong in CorePanel’s application runtime. - Only the tools the jail carries. Anything else has to be added under Settings → Shell jail utilities first.
- On RHEL 8,
psshows nothing. The jail has no/procthere; newer releases list the account’s own processes. - SELinux has to be on. On a server with SELinux disabled, the Shell level is refused, because the jail alone would not confine the account’s own code. SFTP only and Legacy work either way.
Try it
Open an account, go to SSH access and pick Shell from the access level dropdown. The first shell on a server takes about a minute while CorePanel builds the shared copy of the tools; later accounts are quick. Every detail, from limits to keys and what CorePanel writes in sshd, is in the SSH access documentation.