Harden systemd Services with Security Directives
A systemd unit can do more than start and restart a process. It can also define a security boundary around the service by restricting filesystem access, Linux capabilities, namespaces, privilege changes, and resource consumption.
These controls do not replace application security, but they can reduce the damage caused by a compromised or misbehaving process.
Start from the service’s real requirements
Hardening works best when it is based on what the process actually needs.
Before changing a unit, answer:
- Does the service need to write anywhere besides its state and cache directories?
- Does it need access to user home directories?
- Does it need raw sockets, device files, or kernel interfaces?
- Does it need to create new privileges?
- Does it need network access at all?
- What is a reasonable memory or process limit?
The strongest sandbox is one that permits the necessary behavior and rejects everything else.
Prevent privilege escalation
A broadly useful setting is:
[Service]
NoNewPrivileges=yesThis prevents the service and its children from gaining new privileges through mechanisms such as set-user-ID or file capabilities after execution begins.
For ordinary application services that do not intentionally launch privileged helpers, this is often a low-friction restriction.
Restrict filesystem visibility and writes
ProtectSystem= can make system paths read-only or inaccessible to writes.
A strict starting point is:
[Service]
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yesProtectSystem=strict makes the filesystem hierarchy read-only to the service, subject to systemd’s documented exceptions and explicit writable paths. ProtectHome=yes prevents access to typical home-directory locations. PrivateTmp=yes gives the service a private view of temporary directories.
A service still needs somewhere to store state. Prefer systemd-managed directories instead of reopening broad filesystem access:
[Service]
StateDirectory=example-app
CacheDirectory=example-appThese directives create service-specific locations with appropriate ownership semantics and work well with restrictive filesystem settings.
If an application genuinely needs a specific additional writable path, grant only that path rather than weakening the entire sandbox.
Remove capabilities you do not need
Linux capabilities divide some traditional root powers into smaller privileges. A service that does not need privileged kernel operations should not keep a broad capability set.
For a non-privileged application service, consider:
[Service]
CapabilityBoundingSet=
AmbientCapabilities=An empty CapabilityBoundingSet removes capabilities from the bounding set. An empty AmbientCapabilities ensures no ambient capability is passed to executed processes.
Do not copy this blindly into a service that binds privileged ports, manages networking, changes identities, or performs other capability-dependent work. Determine the actual requirement first.
Limit kernel and namespace exposure
Depending on the workload, additional restrictions can remove classes of unnecessary behavior:
[Service]
PrivateDevices=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes
RestrictSUIDSGID=yes
LockPersonality=yesThese options reduce access to devices, kernel tuning interfaces, module operations, control groups, set-user-ID/set-group-ID file creation, and execution personality changes.
Some application runtimes or container-related tools legitimately need access that these settings block. Apply restrictions incrementally and test real workloads, not only service startup.
Add resource boundaries
Security and reliability overlap. A runaway process that consumes all memory or forks without limit can cause an availability incident even without an attacker.
Examples:
[Service]
MemoryMax=1G
TasksMax=256Choose limits from observed workload behavior and leave enough headroom for normal peaks.
An unrealistically low memory limit can turn a recoverable spike into a restart loop. A useful boundary is one that protects the host while allowing expected operation.
Inspect the unit before editing
Distribution packages may install vendor units under system directories. Avoid editing those files directly because package upgrades can replace them.
Use a drop-in override instead:
sudo systemctl edit example.serviceThen place only your overrides in the editor.
After saving changes, reload systemd if your workflow requires it and restart the service during an appropriate maintenance window.
Measure the sandbox
On systems that provide it, systemd-analyze security can inspect a unit and report which hardening controls are enabled:
systemd-analyze security example.serviceTreat the score as a diagnostic aid, not a target. A low numerical exposure score is not proof that the application is secure, and forcing every suggested restriction can break legitimate behavior.
The command is most useful for discovering controls you have not considered and for comparing hardening changes over time.
Test behavior, not just activation
A service that reaches active (running) can still fail later because a restricted operation happens only on a rare code path.
Test:
- startup and shutdown;
- normal reads and writes;
- log rotation or reopening;
- temporary-file creation;
- network connections;
- scheduled maintenance jobs;
- configuration reloads;
- crash recovery;
- application updates.
Review the journal for permission and namespace errors after each hardening step.
Common pitfalls
Applying a copied hardening block wholesale
Security directives are workload-specific. A block that is safe for a stateless HTTP process can break a database, backup agent, or hardware service.
Reopening the whole filesystem after one failure
If the service needs one writable directory, grant that directory. Do not discard the broader read-only policy.
Treating the security score as a compliance result
systemd-analyze security evaluates systemd sandboxing features, not application vulnerabilities, authentication, secrets handling, or network policy.
Forgetting operational paths
Backup, migration, reload, and recovery paths may need permissions that normal request handling does not exercise.
Harden incrementally
A practical sequence is to enable low-risk controls such as NoNewPrivileges and PrivateTmp, restrict home and system paths, define explicit state directories, reduce capabilities, then add kernel and resource restrictions based on the service’s needs.
Apply one group at a time and test realistic behavior. systemd hardening is most effective when the unit file becomes an executable description of the service’s minimum operating privileges, not a collection of security settings copied without context.