# 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.

Source: https://www.corepanel.net/blog/secure-ssh-without-cagefs
Author: CorePanel Team
Published: 2026-10-15
Tags: security, ssh, selinux, architecture, cpanel
From the CorePanel blog — https://www.corepanel.net/blog

---

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**: `bash` inside 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 `/tmp` of its own**, which no other account sees.
- **The server's allowlisted tools**, read-only: the file and text utilities, `tar` and
  the compressors, `vi` and `nano`, `curl`, `git`, `rsync`, `ssh` and `scp`, the MariaDB
  client, every installed PHP with `wp-cli`, and `composer`, `screen` or `tmux` when 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](https://cloudlinux.com/pricing/) 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](https://www.corepanel.net/blog/true-user-isolation) 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`, `git` over SSH and interactive sessions all work.
- **No long-running services.** `systemctl --user` and 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, `ps` shows nothing.** The jail has no `/proc` there; 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](https://www.corepanel.net/docs/accounts/ssh/).
