Skip to content

Isolate Shared Hosting Accounts with Linux Namespaces and cgroups

CageFS licensing costs can erode margins. Learn to build lightweight per-account isolation using native Linux kernel features for a fraction of the price.

Written by AISali·July 30, 2026·5 min read

The Problem with Standard Shared Hosting#

By default, every user on a shared Linux server can see every other user's processes, often read their temporary files in /tmp, and compete for the same CPU and memory. One runaway WordPress plugin or a compromised script can tank the entire box, affecting all customers.

The industry-standard fix is CloudLinux's CageFS, which provides excellent isolation. However, its per-server licensing—typically $12-16/month per server—can significantly erode margins for indie hosts running a handful of VPS nodes. For a reseller operating five servers, that's $60-80/month before you've sold a single account.

What if you could achieve similar process and resource isolation using tools already baked into the Linux kernel? Namespaces and cgroups are the building blocks that power containers like Docker, but you can use them directly to cage individual hosting accounts. This guide walks you through a practical, manual setup for a single shared hosting user.

What You'll Achieve#

By the end of this tutorial, you will have:

  • Process Isolation: A user cannot see or signal other users' processes.
  • Filesystem Isolation: The user sees a minimal view of the system, not the full /.
  • Resource Controls (cgroups): Hard limits on CPU, memory, and process count to prevent noisy neighbors.

Prerequisites: A Linux server (kernel 3.8+ is minimum, but 5.x+ is strongly recommended for cgroups v2), root access, and basic command-line comfort. We'll use unshare, debootstrap, and chroot.

Step 1: Create a Minimal Root Filesystem for the User#

First, build a directory that will act as the user's new root (/). We'll use debootstrap for a minimal Debian/Ubuntu base, but you can adapt this for RPM-based distros using dnf --installroot.

bash
# Install debootstrap if needed
apt-get install debootstrap

# Create the cage directory
mkdir -p /srv/cages/user1

# Install a minimal system into it
debootstrap --variant=minbase bullseye /srv/cages/user1 http://deb.debian.org/debian

This gives you a clean, minimal filesystem. You'll need to bind-mount essential directories for it to function. Create a script /usr/local/bin/setup-cage.sh:

bash
#!/bin/bash
USER=$1
CAGE_DIR="/srv/cages/$USER"

# Essential bind mounts
mount --bind /proc "$CAGE_DIR/proc"
mount --bind /sys "$CAGE_DIR/sys"
mount --bind /dev "$CAGE_DIR/dev"
mount --bind /dev/pts "$CAGE_DIR/dev/pts"
mount --bind /tmp "$CAGE_DIR/tmp"

# Copy DNS config
cp /etc/resolv.conf "$CAGE_DIR/etc/"

Run chmod +x /usr/local/bin/setup-cage.sh and then ./setup-cage.sh user1.

Step 2: Create the User and Set Up Namespaces#

Create the system user and then launch a shell inside the cage using unshare to get a new PID namespace.

bash
# Create the user
useradd -m -s /bin/bash -d /srv/cages/user1/home/user1 user1

# Set a password
passwd user1

# Launch a namespaced shell (PID + Mount namespace)
unshare --pid --mount-proc=/srv/cages/user1/proc --fork chroot /srv/cages/user1 /bin/bash

What's happening here?

  • --pid: Creates a new PID namespace. Processes inside can't see host processes.
  • --mount-proc: Mounts a new /proc inside the cage so ps only shows cage processes.
  • --fork: Necessary for the PID namespace to work correctly.
  • chroot: Changes the apparent root directory to our cage.

You now have a shell where ps aux shows only processes from inside the cage. However, this is manual. We need to automate this for login.

Step 3: Automate with a Custom SSH Wrapper#

We'll replace the user's login shell with a script that sets up the environment. Edit /etc/passwd for user1 to use /usr/local/bin/cage-shell.

Create /usr/local/bin/cage-shell:

bash
#!/bin/bash
CAGE_DIR="/srv/cages/$(whoami)"

# Run the bind-mount setup script (needs root, use sudoers)
sudo /usr/local/bin/setup-cage.sh "$(whoami)"

# Enter the namespaced environment
exec sudo unshare --pid --mount-proc="$CAGE_DIR/proc" --fork chroot "$CAGE_DIR" /bin/login -f "$(whoami)"

You'll need to configure passwordless sudo for specific commands. Add to /etc/sudoers.d/cage:

bash
user1 ALL=(root) NOPASSWD: /usr/local/bin/setup-cage.sh user1
user1 ALL=(root) NOPASSWD: /usr/bin/unshare --pid --mount-proc=/srv/cages/user1/proc --fork chroot /srv/cages/user1 /bin/login -f user1

Security Note: This is a simplified example. In production, you'd use a more robust method like nsenter and a dedicated service, or consider using systemd-nspawn which handles many of these details automatically.

Step 4: Apply Resource Limits with cgroups v2#

This is where you prevent the noisy neighbor. Modern kernels (5.2+) use cgroups v2 by default. We'll create a systemd slice for our user.

Create /etc/systemd/system/user1.slice:

ini
[Unit]
Description=Resource limits for user1

[Slice]
# Limit CPU to 50% of one core
CPUQuota=50%
# Limit memory to 512MB
MemoryMax=512M
MemoryHigh=384M
# Limit processes to 150
TasksMax=150

Then, we need to place the user's processes into this slice. Modify the cage-shell script to launch the final login inside this slice using systemd-run:

bash
# Replace the exec line in cage-shell with:
exec sudo systemd-run --slice=user1.slice --scope \
  unshare --pid --mount-proc="$CAGE_DIR/proc" --fork \
  chroot "$CAGE_DIR" /bin/login -f "$(whoami)"

Enable and start the slice: systemctl enable --now user1.slice.

Step 5: Testing and Verification#

  1. SSH in as user1. You should get a shell.
  2. Check process isolation: Run ps aux. You should only see your own shell and ps.
  3. Check resource limits: Run systemctl status user1.slice on the host. You'll see current memory and task usage.
  4. Stress test: Inside the cage, run stress-ng --vm 2 --vm-bytes 256M --timeout 30s. Watch the host's systemctl status user1.slice to see memory usage climb to the limit. The kernel will throttle the process, protecting other users.

The Caveats and the Real-World Trade-Off#

This is a powerful DIY foundation, but it's not a turnkey solution. You are responsible for:

  • Security Patching: The minimal cage needs regular updates (apt-get upgrade inside each cage).
  • Application Support: Making sure PHP, MySQL, and email work correctly inside the cage requires additional bind mounts and configuration.
  • Management Overhead: Scaling this to 100 users means scripting the creation of cages, users, cgroups, and sudoers rules.

This is precisely why solutions like Salieno Core exist. We've built the management layer, billing integration, and one-click provisioning on top of these same kernel primitives, so you get the isolation without the daily sysadmin burden. But understanding the underlying mechanics makes you a better host and a more informed buyer.

Conclusion#

Linux namespaces and cgroups give you the raw tools to build a secure, multi-tenant shared hosting environment. You can contain processes, limit resources, and create a pseudo-virtual environment for each user without the cost of a commercial container solution.

While the manual setup is educational, the operational reality for a growing business is automation. Start with this knowledge to evaluate solutions—whether you build your own tooling or adopt a platform that has already done the heavy lifting. The key is understanding that isolation isn't optional; it's the foundation of reliable shared hosting.

Share

0 comments

Loading comments…

More from the blog