Skip to content

Harden Linux Services with systemd Sandboxing

systemd can restrict what a service may access, even when its process is compromised. This guide tests a harmless one-shot unit, then shows how to add a small set of filesystem and privilege restrictions to a canary service. The unit settings below are examples: confirm the service’s required files, sockets, devices, and write paths before applying them.

01

Inspect the Unit and Record a Baseline

Read First

Choose a noncritical system service for a later canary test. Read its effective unit and drop-ins before changing anything. Replace app.service below with a real unit name on your distribution; SSH units, for example, may be called ssh.service or sshd.service.

Terminal window
systemd --version | head -n 1
sudo systemctl cat app.service
sudo systemd-analyze security app.service
❯ Illustrative Output (Results Vary)
Review the unit's user, commands, writable paths, devices, and current sandbox settings.
The security report is a checklist for review, not a pass/fail certification.

02

Create a Harmless Sandbox Test Unit

Lab Example

This temporary system unit runs /usr/bin/true once under a transient, unprivileged identity. It has no application data to write, so it is a safe way to check that your systemd version accepts the example settings before you work on a real service.

Terminal window
# Stop if this unit name already exists; choose a new lab name instead.
unit_state=$(systemctl show sandbox-demo.service -p LoadState --value)
test "$unit_state" = not-found || { echo 'Unit name already exists; stop.' >&2; exit 1; }
sudo test ! -e /etc/systemd/system/sandbox-demo.service || exit 1
sudo test ! -L /etc/systemd/system/sandbox-demo.service || exit 1
sudo tee /etc/systemd/system/sandbox-demo.service >/dev/null <<'UNIT'
[Unit]
Description=Tutorial systemd sandbox demonstration
[Service]
Type=oneshot
DynamicUser=yes
ExecStart=/usr/bin/true
NoNewPrivileges=yes
PrivateTmp=yes
ProtectHome=yes
ProtectSystem=strict
ProtectKernelTunables=yes
ProtectControlGroups=yes
UNIT
❯ Illustrative Output (Results Vary)
The command writes the temporary unit file. It does not start or enable the service.

03

Validate the Unit Before Loading It

Configuration Check

Ask systemd to parse the file and check references, then reload the manager so it can see the new unit. A quiet verification run means the unit file passed this check; it does not prove that every sandbox feature is available in a container or that a real application will work under the restrictions.

Terminal window
sudo systemd-analyze verify /etc/systemd/system/sandbox-demo.service
sudo systemctl daemon-reload
❯ Illustrative Output (Results Vary)
No diagnostic output is expected when the unit parses cleanly.

04

Run the Demo and Review Its Restrictions

Test

Inspect the security report, run the one-shot unit, and check its recorded result. Because the command exits immediately, the unit normally returns to inactive after a successful run; use Result and ExecMainStatus to confirm the outcome.

Terminal window
sudo systemd-analyze security sandbox-demo.service
sudo systemctl start sandbox-demo.service
sudo systemctl show sandbox-demo.service -p Result -p ExecMainStatus
sudo journalctl -u sandbox-demo.service -n 10 --no-pager
❯ Illustrative Output (Results Vary)
Result=success
ExecMainStatus=0

05

Apply a Small Set to a Canary Service

Controlled Change

For a service you own, create an override rather than editing its vendor unit. Add only the settings the application can tolerate, then validate and test after a planned restart. A service using ProtectSystem=strict may need a managed state directory or a narrowly scoped writable path.

Terminal window
sudo systemctl edit app.service

Add a reviewed subset under [Service] in the editor:

[Service]
NoNewPrivileges=yes
PrivateTmp=yes
ProtectHome=yes
ProtectSystem=strict
ProtectKernelTunables=yes
ProtectControlGroups=yes

Then validate, reload, and test during an approved maintenance window:

Terminal window
sudo systemd-analyze verify app.service
sudo systemctl daemon-reload
sudo systemctl restart app.service
sudo systemctl is-active app.service
sudo journalctl -u app.service -n 50 --no-pager
❯ Illustrative Output (Results Vary)
Confirm the service is active and its normal health checks, logs, and client requests still succeed.

06

Remove the Demo or Roll Back the Canary

Recovery

If the canary fails its health checks, edit its drop-in and remove only the directives added for this test, then reload and restart it. After the lab, remove the exact tutorial unit file so it does not remain on the host.

Terminal window
# Lab cleanup: removes only the demo unit created in Step 2.
sudo systemctl stop sandbox-demo.service
sudo systemctl reset-failed sandbox-demo.service
sudo rm /etc/systemd/system/sandbox-demo.service
sudo systemctl daemon-reload
❯ Illustrative Output (Results Vary)
The temporary unit is removed from the host and systemd's unit cache is refreshed.

Comments