Security and troubleshooting

Reduce service privileges

Start with a dedicated account and add systemd sandboxing controls that match what the application needs.

A service should get only the files, capabilities, devices, and kernel interfaces its job needs. Nothing more. If the process gets compromised, the sandbox decides what the attacker actually holds.

Step one is always a dedicated unprivileged account, User=demo-api. Every sandboxing directive below builds on top of that. Without it, none of them matter much.

Measure before you harden

systemd will grade your unit for you:

systemd-analyze security demo-api.service
  NAME                        DESCRIPTION                                EXPOSURE
✗ PrivateTmp=                 Service has access to other software's...
✗ ProtectSystem=              Service has full access to the OS file...
✗ NoNewPrivileges=            Service processes may acquire new privi...
...
→ Overall exposure level for demo-api.service: 9.2 UNSAFE

Don’t panic at the word UNSAFE. The score is a heuristic, not a verdict. A plain unit with just User= scores around 9 out of 10. The value is in the sorted list. It shows which missing control exposes the most, so you know where to start.

The high-value controls

Put these in a drop-in with sudo systemctl edit demo-api.service, not in the vendor unit:

[Service]
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict
ProtectHome=yes
ReadWritePaths=/var/lib/demo-api

NoNewPrivileges=yes blocks the process from gaining privileges through setuid binaries like sudo. PrivateTmp=yes gives it a private /tmp, so it cannot read other services’ temp files.

ProtectSystem=strict mounts the entire filesystem read-only for this service. ReadWritePaths= then carves out the directories it genuinely writes. ProtectHome=yes hides every home directory.

One more worth knowing: CapabilityBoundingSet= with an empty value drops all capabilities. That is right for most services that listen on a high port and talk to a database. They need no special kernel powers at all.

After saving the drop-in, restart and run the security check again. The score drops and the list gets shorter.

Apply gradually, and expect breakage

Add controls one or two at a time. An incompatible control stops the service from starting correctly, and if you added six at once you have no idea which one did it.

The typical failure after ProtectSystem=strict looks like this in the journal:

demo-api.service: Error: EROFS: read-only file system, open '/var/lib/demo-api/cache.json'

That is the sandbox working. The app tried to write somewhere you did not list. The fix is not removing the protection. It is adding the specific path to ReadWritePaths=, restarting, and testing again.

Try this on one of your services. Run systemd-analyze security and pick the single highest exposure. Add one compatible control in a drop-in, restart, and verify the application still works. Then take the next one. Three rounds of this usually gets a typical web service from “UNSAFE” to “MEDIUM” without breaking anything.

Lesson completed

Take this course offline

Get every free book, course edition, and software download.

Get the download library →