systemd’s namespace isolation features, specifically ProtectSystem, ProtectHome, and ProtectKernelTunables, are designed to create robust security sandboxes for your services by severely restricting what files and directories they can access, and how they can interact with the kernel.

Let’s see this in action with a simple service. Imagine we have a script that tries to write to /etc, read from /home, and then access kernel parameters.

# /usr/local/bin/sandbox-test.sh
#!/bin/bash

echo "Attempting to write to /etc..."
if echo "test" > /etc/sandbox-test.txt; then
  echo "Successfully wrote to /etc!"
else
  echo "Failed to write to /etc."
fi

echo "Attempting to read from /home..."
if [ -f /home/user/somefile ]; then
  echo "Successfully read from /home/user/somefile!"
else
  echo "Failed to read from /home/user/somefile."
fi

echo "Attempting to read kernel tunables..."
if [ -r /proc/sys/kernel/hostname ]; then
  echo "Successfully read /proc/sys/kernel/hostname!"
else
  echo "Failed to read /proc/sys/kernel/hostname."
fi

echo "Attempting to write kernel tunables..."
if echo "test-hostname" > /proc/sys/kernel/hostname; then
  echo "Successfully wrote to /proc/sys/kernel/hostname!"
else
  echo "Failed to write to /proc/sys/kernel/hostname."
fi

exit 0

Now, let’s create a systemd service file to run this script without any isolation.

# /etc/systemd/system/sandbox-test-unprotected.service
[Unit]
Description=Sandbox Test - Unprotected

[Service]
Type=oneshot
ExecStart=/usr/local/bin/sandbox-test.sh
User=root
Group=root

[Install]
WantedBy=multi-user.target

If we enable and start this service:

sudo systemctl enable sandbox-test-unprotected.service
sudo systemctl start sandbox-test-unprotected.service
sudo systemctl status sandbox-test-unprotected.service

The output will show that all operations succeed. The script will write to /etc, read from /home, and modify kernel tunables.

Now, let’s introduce ProtectSystem=strict. This directive remounts /usr, /boot, /etc, and /run as read-only, and also hides /var and /tmp if they are not explicitly needed.

# /etc/systemd/system/sandbox-test-protected-system.service
[Unit]
Description=Sandbox Test - ProtectSystem

[Service]
Type=oneshot
ExecStart=/usr/local/bin/sandbox-test.sh
User=root
Group=root
ProtectSystem=strict

After running sudo systemctl daemon-reload, sudo systemctl restart sandbox-test-protected-system.service, and sudo systemctl status sandbox-test-protected-system.service, the output will now show failures for writing to /etc.

Attempting to write to /etc...
Failed to write to /etc.
Attempting to read from /home...
Failed to read from /home/user/somefile.
Attempting to read kernel tunables...
Successfully read /proc/sys/kernel/hostname!
Attempting to write kernel tunables...
Failed to write to /proc/sys/kernel/hostname.

Notice that reading from /home also failed. This is because ProtectSystem=strict also implies ProtectHome=true by default, which hides /home, /root, and /users directories.

To allow specific access, we can use ReadWritePaths, ReadOnlyPaths, and InaccessiblePaths. For instance, if our service legitimately needs to write to a specific directory within /var:

# /etc/systemd/system/sandbox-test-custom-paths.service
[Unit]
Description=Sandbox Test - Custom Paths

[Service]
Type=oneshot
ExecStart=/usr/local/bin/sandbox-test.sh
User=root
Group=root
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/log/my-app

With this configuration, the service can now write to /var/log/my-app (provided the directory exists and the user/group has permissions), but still cannot write to /etc.

ProtectKernelTunables=true is another crucial directive. It remounts /proc/sys read-only and prevents modification of kernel tunable parameters. This is essential for preventing a compromised service from altering system behavior or escalating privileges.

The most surprising true thing about systemd’s namespace isolation is how granular and powerful it is, allowing you to build truly hardened services with minimal configuration. It doesn’t just hide things; it actively remounts filesystems read-only or makes entire directory trees inaccessible, creating strong barriers.

Let’s consider a service that needs to write logs to /var/log/myapp and read configuration from /etc/myapp.conf.

# /etc/systemd/system/my-app.service
[Unit]
Description=My Application Service

[Service]
Type=simple
ExecStart=/usr/local/bin/my-app-server
User=appuser
Group=appgroup
# Secure the system from this service
ProtectSystem=full
ProtectHome=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
ProtectClock=true
ProtectHostname=true
ProtectIdmap=true
ProtectUlimits=true
# Allow necessary access
ReadOnlyPaths=/etc/myapp.conf
ReadWritePaths=/var/log/myapp
# If /var/log/myapp doesn't exist, create it first with correct ownership:
# sudo mkdir -p /var/log/myapp
# sudo chown appuser:appgroup /var/log/myapp

In this setup, ProtectSystem=full makes / read-only except for /var, /run, and /tmp, which are then managed by other directives. ProtectHome=true hides /home, /root, and /users. ProtectKernelTunables=true locks down /proc/sys. The ReadOnlyPaths and ReadWritePaths explicitly grant the minimal necessary access.

The Protect directives are not just about file access; they extend to kernel interfaces and system resources. For example, ProtectKernelModules=true prevents loading or unloading kernel modules, ProtectControlGroups=true isolates cgroup access, and ProtectClock=true prevents time synchronization manipulation.

The most counterintuitive aspect of these directives is their interaction with existing mount points. When ProtectSystem=strict is used, systemd doesn’t just remount /usr read-only; it effectively creates a new, read-only view of /usr for the service’s namespace. If you have a symlink within /usr pointing outside /usr, that symlink will still be resolved relative to the original filesystem, not the new read-only view, which can lead to unexpected behavior if not carefully considered.

If you configure ProtectSystem=strict but then try to write to /etc/hosts, you’ll receive a "Read-only file system" error, even though /etc is generally writable by root on the host. This is because ProtectSystem=strict makes /etc itself read-only within the service’s namespace.

The next concept you’ll likely encounter is how to manage network access for sandboxed services, which is controlled by directives like PrivateNetwork, BindPaths, and NoNewPrivileges.

Want structured learning?

Take the full Systemd course →