Harden a systemd Service with systemd-analyze security and Sandboxing
Reduce the damage a compromised service can do by using systemd's built-in security analysis and a careful first pass of sandboxing controls.
How to score a unit with systemd-analyze security, add a drop-in override, apply a small set of safer restrictions, and re-test the service instead of blindly pasting a giant hardening checklist.
Operators already comfortable with basic unit files who want a better default security posture for long-running services.
The wrong restriction can break file access, networking, or startup behavior. Start small and reversible.
Before you begin
- A service unit you already understand at a basic level.
- Root or sudo access on the host.
- A way to test the service after each change.
- Log access through
journalctl.
The current systemd documentation describes systemd-analyze as both an analysis and verification tool, and one of its most practical subcommands for operators is systemd-analyze security. That command does not magically harden a service for you, but it does expose where a unit is still running with broad privileges and few containment controls. The right workflow is to inspect, apply a small reversible change, and test again.
Step 1: Score the current unit
Start by checking the unit's current exposure:
systemd-analyze security myapp.serviceThis output highlights categories such as filesystem access, privilege boundaries, capabilities, and sandboxing features. Treat the score as a guide, not a badge. A worse score does not automatically mean the app is insecure in context, and a better score does not prove the app is safe. What it does give you is a concrete list of hardening levers you can evaluate.
Also review the current unit and any existing overrides:
systemctl cat myapp.service
systemctl status myapp.service --no-pagerUnderstanding the current unit shape matters because some restrictions are only safe for services that do not need write access, raw networking, or privileged filesystem paths.
Step 2: Use a drop-in override, not a direct edit
Do not edit packaged unit files in place if you can avoid it. Use a drop-in override so your changes are easier to audit and less likely to be lost during package updates.
sudo systemctl edit myapp.serviceThis opens an override file under /etc/systemd/system/myapp.service.d/override.conf. That is the right place for your first hardening pass.
Step 3: Apply a safe first hardening pass
Start with controls that are broadly useful and usually low-risk for many web apps and background services:
[Service]
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
ProtectHome=true
ProtectControlGroups=true
ProtectKernelModules=true
ProtectKernelTunables=true
RestrictSUIDSGID=true
LockPersonality=true
MemoryDenyWriteExecute=trueNot every one of these is safe for every service, but as a first pass they are a practical review set. The key is that they are understandable:
NoNewPrivileges=trueprevents the service from gaining new privileges through exec transitions.PrivateTmp=trueisolates its temporary directory view.ProtectSystem=fullmakes much of the system tree read-only.ProtectHome=trueblocks casual access to user home directories.
If the service needs a writable location, provide it explicitly instead of leaving the whole filesystem broad by default. For example:
[Service]
ReadWritePaths=/var/lib/myapp /var/log/myappThis is where real service knowledge matters. A safe hardening pass narrows access deliberately rather than pretending the service can run with no state at all.
Step 4: Restart and verify behavior
After saving the drop-in, reload systemd and restart the service:
sudo systemctl daemon-reload
sudo systemctl restart myapp.service
sudo systemctl status myapp.service --no-pager
journalctl -u myapp.service -n 100 --no-pagerThen test the service from the perspective that matters. If it is a web app, make an HTTP request. If it is a worker, run a controlled task. If it writes to disk, verify that write path still works.
Finally, rescore the unit:
systemd-analyze security myapp.serviceYou are looking for two things at once: a tighter security profile and a service that still behaves correctly.
Step 5: Tighten based on real service needs
Once the first pass works, you can go further based on what the service actually does. Examples include:
ProtectClock=trueif the service does not need to adjust time.PrivateDevices=trueif it does not need device access.CapabilityBoundingSet=to strip Linux capabilities it should not keep.RestrictAddressFamilies=to limit the socket families it can use.
These stronger controls are where many breakages begin, so add them one by one. The systemd tools give you inspection, but you still have to reason about the application.
Rollback and recovery notes
If the service fails after a hardening change, remove the last restriction first instead of tearing down the whole hardening layer blindly. The clean rollback path is the override file itself.
sudo systemctl revert myapp.service
sudo systemctl daemon-reload
sudo systemctl restart myapp.serviceIf you only need to undo one new directive, edit the drop-in, remove that line, reload, and test again. Keep a copy of the working override in version control or an ops repo so good hardening changes do not become one-off shell history.
Troubleshooting common systemd hardening problems
The service starts failing with permission errors.
Check whether ProtectSystem, ProtectHome, or missing ReadWritePaths blocked a location the app actually needs.
The unit no longer reaches the network correctly.
Review restrictions such as RestrictAddressFamilies or capability changes if you added them. Start from the last known-good step, not from zero.
The score improved, but I do not understand why.
Read the systemd-analyze security categories alongside the actual directives in your drop-in. The point is operating understanding, not chasing a prettier number.
The packaged unit changed after an update.
That is exactly why the drop-in approach matters. Use systemctl cat to review the new combined unit and confirm your override still makes sense.
What to do next
Once a service is durable and more contained, the next useful step is protecting its browser-facing admin path. Continue with Protect Internal Dashboards with Authelia, Nginx, and an Identity-Aware Gate.
