Systemd Hardening: A Methodical Guide to Sandboxing Services

Systemd hardening is the practice of using the service manager’s own sandboxing directives to restrict what a service can read, write, execute, and reach if the process is compromised. The mechanism is a set of options placed in a unit file or drop-in, and the principal diagnostic is systemd-analyze security. The central methodological point, repeated across vendor documentation, is that the discipline lies in profiling each service and validating each restriction, not in enabling every available directive.
This article describes the scoring tool and its limits, the directives that matter most, an incremental procedure for applying them, the failure modes that commonly appear, and the relationship between systemd restrictions and mandatory access control. It is written for administrators and technical leaders who maintain Linux hosts and need a defensible, testable approach.
TL;DR
systemd-analyze securityyields an exposure estimate from 0.0 to 10.0 for each unit. It measures systemd-managed sandboxing only and is a prioritization aid, not a vulnerability verdict.- Apply restrictions incrementally through drop-ins (
systemctl edit), restart the unit after each change, and exercise real workflows, because indiscriminate hardening commonly breaks network access, sockets, and JIT runtimes. - Systemd restrictions reduce blast radius but do not replace patching, secure application design, logging, or SELinux and AppArmor; they are most effective when layered with them.
- A lower score is not an acceptance test. Acceptance means startup, health checks, upgrades, backups, and monitoring all function under the production workload.
Table of Contents
- What systemd hardening addresses
- Reading the exposure score
- The directives that carry the most weight
- An incremental procedure
- Common failure modes
- Acceptance: beyond a lower number
- Layering with SELinux and AppArmor
- Our perspective
- When outside review is warranted
- FAQ
- Sources
What systemd hardening addresses
Many services run with privileges they do not need in order to function. The Rocky Linux documentation on unit hardening frames the purpose as minimizing the consequences of a compromised process by enforcing security measures and limiting permissions through tools that systemd already ships. A default unit file typically specifies only how to start the process, which leaves the service with broad visibility of the filesystem, the device tree, the network stack, and the kernel interface.
Red Hat’s engineer Zbigniew Jędrzejewski-Szmek argues in a discussion of using systemd features to secure services that centralizing privilege reduction in the service manager removes duplicated security logic from individual services and abstracts differences among kernels and distributions. A consequence is that the technique has value even for applications written in high-level languages, and that it can provide container-like isolation for traditional services without a migration to containers.
The scope should nonetheless be stated precisely. The same Red Hat guidance indicates that hardening reduces blast radius but does not substitute for patching, secure application design, network controls, identity management, logging, or mandatory access control. The relevant planning question, which SUSE’s operational framing supports, is which outcomes of a service compromise are unacceptable, and which restrictions reduce those outcomes while preserving documented service functionality.
Reading the exposure score
The command systemd-analyze security is the usual entry point. According to the systemd-analyze documentation, it computes a per-unit exposure estimate between 0.0 and 10.0, where higher values indicate less sandboxing and lower values indicate tighter restrictions. It can inspect a single unit or all loaded long-running service units, and current documentation describes JSON output, offline analysis, thresholds, and custom JSON security policies.
What the score measures
The score reflects only the sandbox settings that systemd manages. It does not account for protections implemented inside the service, and it can miss risk that passes through permitted inter-process communication such as D-Bus. A service with a high exposure value is not thereby shown to be vulnerable, and a service with a low value is not thereby shown to be secure. The documentation is explicit that the figure is an estimate of exposure, not a statement about vulnerability.
A practical corollary is that a low score demands functional and threat-model validation. The systemd maintainers also observe that individual sandbox controls may be circumvented when used alone; for example, mount-related privilege can undermine filesystem restrictions. Hardening quality therefore depends on the combination of controls and on service boundaries, not on maximizing a checklist.
A reasonable use of the number
The score is most useful for triage. Running the command across all loaded services produces a ranked list, and services that are network-facing, run as root, or process untrusted input can be examined first. Treating the number as a regression metric across releases is also reasonable, provided the version caveat below is observed.
systemd-analyze security
systemd-analyze security nginx.service
Scoring, defaults, available directives, and kernel support vary across systemd releases, so a profile validated on one distribution should be re-verified on another. The archived man page for systemd 250 and the current documentation differ in features, which illustrates the point.
The directives that carry the most weight
Systemd’s service-execution documentation, summarized in Red Hat’s overview of systemd sandboxing, lists a number of controls that act on distinct surfaces. They are complementary, not interchangeable, and a service generally needs a profile derived from its actual system calls, files, sockets, devices, and privilege requirements.
| Directive | Surface restricted | Principal caution |
|---|---|---|
ProtectSystem= |
Mutation of system directories | Services that write to /usr or /etc need explicit exceptions |
ProtectHome= |
Exposure of /home, /root, /run/user |
Breaks services that legitimately read user data |
PrivateTmp= |
Shared temporary files | Affects services that exchange files through /tmp |
PrivateDevices= |
Access to physical devices | Unsuitable for services that need hardware |
NoNewPrivileges= |
Privilege escalation through setuid or file capabilities | Affects helpers that rely on setuid binaries |
CapabilityBoundingSet= |
Linux capabilities | Requires knowing which capabilities the service uses |
RestrictAddressFamilies= |
Network socket families | Too narrow a list breaks Unix sockets or IPv6 |
RestrictNamespaces= |
Namespace creation | May affect services that sandbox their own children |
SystemCallFilter= |
Permitted system calls | Must be validated against the actual workload |
MemoryDenyWriteExecute= |
Writable and executable memory | Can break JIT runtimes |
Directives with specific guidance
Red Hat’s service-sandboxing guidance notes that PrivateNetwork= is appropriate only for services that need no external network communication, and that DynamicUser= is most useful for stateless applications. It also describes SystemCallFilter=@system-service as a starting syscall group that must be validated against the workload, and not as a finished policy.
The Rocky Linux documentation lists a set of commonly useful restrictions: NoNewPrivileges=true, ProtectClock=true, SystemCallArchitectures=native, RestrictNamespaces=true, RestrictSUIDSGID=true, LockPersonality=true, RestrictRealtime=true, RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX, MemoryDenyWriteExecute=true, and ProtectHostname=true. The same page cautions that MemoryDenyWriteExecute can break applications based on JIT compilation, including JavaScript, Java, and .NET runtimes. Its worked example applies the settings through an override, runs daemon-reload, restarts httpd, and then re-checks exposure, arriving at an overall level of 3.0 for that service.
The example is instructive for method as well as for the figure. The reported value belongs to one service, one distribution release, and one configuration, and should not be generalized into an expected outcome for other workloads.
An incremental procedure
The literature converges on layered, incremental application. Red Hat’s guidance suggests the following sequence: establish a dedicated non-root identity, then restrict filesystem visibility and writability, devices, capabilities, namespaces, network families, system calls, and resources. After each change the unit is restarted and its real workflows are exercised. The following procedure formalizes that sequence.
- Baseline. Record the current exposure score, the unit’s behavior, its log output, and the configuration of any health checks. Without a baseline, regressions cannot be attributed.
- Create a drop-in. Use
systemctl edit service-nameso that the package-managed base unit is preserved. Editing vendor-owned unit files directly risks loss of changes on upgrade and complicates rollback. - Establish identity. Confirm that the service runs as a dedicated unprivileged user, or evaluate
DynamicUser=where the application is stateless. - Restrict the filesystem. Introduce
ProtectSystem=,ProtectHome=, andPrivateTmp=, then addReadWritePaths=for the directories the service legitimately writes. - Reduce privileges. Set
NoNewPrivileges=trueand narrowCapabilityBoundingSet=to the capabilities the service demonstrably uses. - Constrain the network and kernel surface. Apply
RestrictAddressFamilies=,RestrictNamespaces=, and the remaining kernel-facing options. - Filter system calls last. Begin with a documented group and validate against the full workload, since this is the directive most likely to produce failures that are difficult to diagnose.
- Reload and restart. Run
systemctl daemon-reloadand restart the service after every change, not after a batch of changes. - Re-measure and re-test. Compare the new score with the baseline, then exercise the full set of behaviors described in the acceptance section below.
- Retain a rollback path. Keep the previous drop-in, or the ability to remove it with
systemctl revert, until the change has survived a full operating cycle.
systemctl edit example.service
# add [Service] directives, save
systemctl daemon-reload
systemctl restart example.service
systemd-analyze security example.service
SUSE’s documentation states that service hardening requires root privileges and a working knowledge of the systemd environment, and gives an introductory effort of under 15 minutes. That figure applies to the introductory exercise in the SUSE guide to securing systemd services; production-grade profiling and regression testing require materially more operational work, which should be planned for explicitly.
Common failure modes
Applying every available directive indiscriminately commonly breaks legitimate behavior. The categories that recur are network access, Unix sockets, JIT runtimes, plugins, and dynamically generated files.
- Network and socket failures. A narrow
RestrictAddressFamilies=list that omitsAF_UNIXorAF_INET6can silently disable local IPC or dual-stack connectivity. The Rocky Linux example includesAF_UNIXfor this reason. - JIT runtime crashes.
MemoryDenyWriteExecute=trueis incompatible with runtimes that generate executable code at run time. - Plugin and extension loading. Services that load modules, or that write generated files outside their declared paths, fail when
ProtectSystem=and related options are applied without matchingReadWritePaths=entries. - Privileged helper failures.
NoNewPrivileges=trueprevents setuid helpers from elevating, which can break mail delivery, authentication helpers, and similar components. - Upgrade-time breakage. Post-install scripts, certificate renewal, and log rotation may touch paths the sandbox now hides. These failures often appear days after the change, not at restart.
User units
The ArchWiki entry on systemd sandboxing notes that user units cannot be hardened or sandboxed to the same degree as system units, because the necessary mechanisms require privilege. This limitation is relevant to developer workstations, where services started with systemd --user do not receive the same isolation as system services.
Acceptance: beyond a lower number
A score improvement can conceal an availability regression, as the SUSE guidance implies. The meaningful acceptance test therefore covers successful startup, health checks, logs, privileged operations, persistence, upgrades, backups, monitoring, and incident-response procedures under the exact production workload. A hardened unit that passes a smoke test in staging but fails during the monthly certificate renewal has traded one risk for another.
For organizations with change-control obligations, the drop-in, the baseline score, the test results, and the rollback procedure are worth recording together. The record converts hardening from an individual administrator’s preference into an auditable control.
Layering with SELinux and AppArmor
Systemd restrictions operate independently of mandatory access control. Red Hat identifies overlap but substantial independence between the two and recommends combining them, and SUSE similarly describes systemd restrictions as an additional layer to be paired with SELinux or AppArmor, not treated as a replacement. The practical implication is that disabling SELinux because a hardened unit failed is the wrong response; the failure should be diagnosed in the logs of both layers.
Further layers belong to adjacent disciplines. Host-level baselines are addressed in a seven-category system hardening checklist, remote access in a staged SSH hardening rollout, and the discipline of keeping vulnerable components current in a patch management strategy. Where unit drop-ins are distributed across many hosts, testing Ansible hardening in staging before production applies directly, and the guidance on Linux automation with systemd timers and safe rollouts covers staged deployment of the changes.
Our perspective
In our technical advisory work on Linux infrastructure, we treat systemd hardening as an engineering exercise with a defined acceptance criterion, not as a scoring exercise. The most frequent error we would expect an organization to make is to run the analysis tool, observe a collection of unfavorable ratings, and attempt to remediate every one in a single change window. The result is a service outage attributed to security, and a lasting institutional reluctance to repeat the work.

We hold that the sequence of decisions matters more than the directives themselves. First, determine which services face untrusted input or hold sensitive data, since these justify the effort. Second, state which compromise outcomes are unacceptable. Third, apply restrictions one at a time through drop-ins and test the actual business function, not merely the process state. Fourth, document the result so that a successor can understand why a restriction exists.
This position follows from the premise on which we build our practice: clarity under pressure is a property of the process, and it is cheapest to establish before an incident. A hardened unit with a tested rollback path allows an operator to act calmly when a service fails at an inconvenient hour, whereas an undocumented profile compounds the pressure it was meant to reduce.
— Jesse Hart
When outside review is warranted
Organizations without in-house Linux specialists, or with many heterogeneous services, may find that profiling and regression testing consume more effort than the initial directive changes. PROJECT-JTH offers technical consulting that includes systems and risk review and infrastructure automation and review; details are available on the technical consulting page. The engagement scope is established individually, and no standard price is published.
FAQ
Does a low systemd-analyze security score mean a service is secure?
No. The score estimates exposure based on systemd-managed sandbox settings only, and the official documentation states that it is not a determination of whether a service is vulnerable. It does not capture protections inside the application, and it can miss risk through permitted IPC such as D-Bus. A low score should be followed by functional testing and a threat-model review.
Should hardening directives be added to the vendor unit file?
It is operationally safer to use a drop-in created with systemctl edit, which preserves the package-managed base unit. After saving, run daemon-reload and restart the service. Retain a tested rollback path, because directives and defaults vary across systemd releases and distributions.
Can systemd hardening replace SELinux or AppArmor?
No. Red Hat and SUSE both describe the two as complementary: systemd restrictions apply independently of SELinux, and SELinux or AppArmor provides an additional policy layer where supported. The recommended strategy is layered, with each control compensating for the limits of the other.
Why does MemoryDenyWriteExecute break some applications?
The directive prohibits memory regions that are both writable and executable, which is the pattern used by just-in-time compilers. The Rocky Linux documentation warns that it can break JIT-based JavaScript, Java, and .NET applications. Enable it only after confirming that the runtime does not depend on such memory.
How long does hardening a service take?
SUSE’s documentation gives an introductory effort of under 15 minutes for the basic procedure. Production-grade work takes materially longer, since it includes profiling the service’s files, sockets, and system calls, then regression testing under the real workload.
Sources
- systemd-analyze documentation (freedesktop.org)
- systemd-analyze man page, systemd 250
- Red Hat: Mastering systemd, securing and sandboxing services
- Red Hat: Using systemd features to secure services
- SUSE: Securing systemd Services
- Rocky Linux: Systemd Units Hardening
- ArchWiki: systemd/Sandboxing
