Security and troubleshooting
Reduce service privileges
Start with a dedicated account and add systemd sandboxing controls that match what the application needs.
8 minute lesson
A service should receive only the files, capabilities, devices, and kernel interfaces required for its job. If the process gets compromised, the sandbox decides what the attacker actually holds.
Step one is always a dedicated unprivileged account (User=demo-api). The sandboxing directives build on top of that.
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
The score is a heuristic, not a verdict. Its value is the sorted list: it shows which missing control exposes the most.
The high-value controls
Useful controls include NoNewPrivileges=, PrivateTmp=, ProtectSystem=, ProtectHome=, and CapabilityBoundingSet=. Put them in a drop-in (sudo systemctl edit demo-api.service), not 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. PrivateTmp=yes gives it a private /tmp. ProtectSystem=strict mounts the entire filesystem read-only for this service, with ReadWritePaths= carving out the directories it genuinely writes. ProtectHome=yes hides home directories. CapabilityBoundingSet= with an empty value drops all capabilities — right for most services that just listen on a high port and talk to a database.
Apply gradually, and expect breakage
Apply them gradually because an incompatible control can prevent correct startup. 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 fix is not removing the protection — it is adding the specific path to ReadWritePaths=, restarting, and retesting.
Run systemd-analyze security for one of your services. Choose one high-impact exposure, add a compatible control in a drop-in, restart, and verify the application still works before moving to the next one.
Lesson completed