funwithlinux guide

Systemd and Security: How to Harden Your Services

In the modern Linux ecosystem, **systemd** has emerged as the de facto init system, managing system processes, services, and boot sequences across most major distributions (e.g., Ubuntu, Fedora, Debian, and Red Hat Enterprise Linux). As the backbone of service management, systemd plays a critical role in securing your infrastructure: misconfigured systemd services are a common attack vector for privilege escalation, data leaks, and service disruptions. Consider this: A 2023 report by the Linux Foundation found that **34% of critical security incidents** in Linux environments stemmed from improperly configured service units, enabling attackers to exploit weak isolation, excessive privileges, or unfiltered network access. Hardening systemd services isn’t just a best practice—it’s a foundational step in securing your Linux systems. This blog will guide you through systemd’s robust security features, from basic service file tweaks to advanced sandboxing techniques, helping you lock down your services and reduce your attack surface.

Table of Contents

  1. Understanding Systemd Service Files
  2. Key Systemd Security Directives
  3. Advanced Sandboxing Techniques
  4. Auditing and Monitoring Systemd Services
  5. Best Practices for Maintaining Hardened Services
  6. Conclusion
  7. References

1. Understanding Systemd Service Files

Before diving into hardening, let’s review the structure of a systemd service file (.service). These files define how services start, run, and stop. They are typically stored in /usr/lib/systemd/system/ (distro-provided) or /etc/systemd/system/ (custom overrides).

A basic .service file has three main sections:

[Unit]  
Description=Example Service  
After=network.target  

[Service]  
ExecStart=/usr/bin/example-daemon  
Restart=on-failure  

[Install]  
WantedBy=multi-user.target  
  • [Unit]: Metadata (description, dependencies).
  • [Service]: Core execution settings (binary path, restart policy, security directives).
  • [Install]: Installation targets (when the service should start).

Critical Note: To customize a service, avoid editing distro-provided files directly. Instead, use systemctl edit <service> to create an override file in /etc/systemd/system/<service>.d/override.conf. This ensures updates to the original service file won’t overwrite your changes.

2. Key Systemd Security Directives

Systemd provides dozens of security-focused directives in the [Service] section. Below, we break down the most impactful ones, grouped by purpose.

2.1 Isolating Service Environments

Isolation prevents a compromised service from affecting other parts of the system. Use these directives to wall off the service’s environment:

PrivateTmp=yes

Creates a private, isolated /tmp and /var/tmp directory for the service. Prevents temp file hijacking attacks (e.g., symlink races).

Example:

[Service]  
PrivateTmp=yes  

PrivateDevices=yes

Hides physical devices (e.g., /dev/sda) and provides a minimal /dev with only essential devices (e.g., /dev/null, /dev/zero). Blocks direct hardware access.

PrivateUsers=yes

Maps the service’s user/group IDs to a private namespace, isolating it from the system’s user database. Prevents user enumeration and privilege escalation via user namespace attacks.

MountFlags=slave

Makes all mount points private to the service, preventing it from modifying the host’s mount namespace (e.g., mounting malicious filesystems).

2.2 Reducing Privileges

Running services as root is a major risk. Use these directives to drop privileges:

User=, Group=

Run the service as a non-root user/group. Always create a dedicated, least-privilege user (e.g., nginx for the Nginx service).

Example:

[Service]  
User=nginx  
Group=nginx  

DynamicUser=yes

Creates a temporary, ephemeral user/group for the service (no persistent entry in /etc/passwd). Ideal for services that don’t need a persistent identity (e.g., logging daemons).

NoNewPrivileges=yes

Prevents the service from gaining new privileges via setuid/setgid binaries, capabilities, or file capabilities. Blocks common privilege escalation paths.

Why it matters: Even if a service is compromised, an attacker can’t use sudo or su to gain root access.

2.3 Protecting the Filesystem

Restrict which parts of the filesystem the service can read/write to:

ProtectSystem=strict

Makes /usr, /boot, and /etc read-only. Use ReadWritePaths= to explicitly allow write access to specific directories (e.g., logs, caches).

Example:

[Service]  
ProtectSystem=strict  
ReadWritePaths=/var/log/nginx /var/cache/nginx  

ProtectHome=yes

Hides user home directories (/home, /root, /run/user) by mounting them as empty, read-only, or tmpfs. Prevents access to sensitive user data.

ReadOnlyPaths=, InaccessiblePaths=

  • ReadOnlyPaths=/etc : Makes /etc read-only (even if ProtectSystem is not set).
  • InaccessiblePaths=/proc/kcore : Hides sensitive paths entirely (service sees them as non-existent).

TemporaryFileSystem=/tmp

Mounts a tmpfs on /tmp (similar to PrivateTmp but more flexible; can be used for other directories).

2.4 Restricting Network Access

Limit the service’s network activity to only what’s necessary:

PrivateNetwork=yes

Disables network access entirely. Use for services that don’t need the network (e.g., local database tools).

IPAddressAllow=, IPAddressDeny=

Whitelist/blacklist IP addresses or CIDRs the service can communicate with.

Example (allow only localhost and 192.168.1.0/24):

[Service]  
IPAddressAllow=127.0.0.1/32  
IPAddressAllow=192.168.1.0/24  
IPAddressDeny=any  

RestrictAddressFamilies=AF_INET AF_INET6

Limit the service to specific network protocols (e.g., IPv4/IPv6 only). Blocks less common families like AF_UNIX (local sockets) or AF_PACKET (raw sockets).

2.5 Limiting Process Capabilities

Linux capabilities (e.g., CAP_NET_BIND_SERVICE, CAP_SYS_ADMIN) grant granular privileges without full root access. Use these directives to restrict capabilities:

CapabilityBoundingSet=CAP_NET_BIND_SERVICE

Defines the only capabilities the service can use. Start with an empty set and add only what’s needed.

Example (Nginx needs to bind to port 80/443):

[Service]  
CapabilityBoundingSet=CAP_NET_BIND_SERVICE  
AmbientCapabilities=CAP_NET_BIND_SERVICE  # Inherit the capability after dropping root  

AmbientCapabilities=

Passes capabilities to the service after it drops root privileges (works with User=).

DropCapabilities=ALL

Drops all capabilities except those explicitly allowed in CapabilityBoundingSet.

2.6 Controlling System Calls

System calls (syscalls) are the interface between user-space and the kernel. Restricting them blocks low-level attacks:

SystemCallFilter=@system-service

Allows a predefined set of syscalls required for most services. Add/remove specific syscalls with +/-.

Example (allow read, write, open; block execve):

[Service]  
SystemCallFilter=read write open  
SystemCallFilter=-execve  # Block execve to prevent spawning new processes  

SystemCallArchitectures=native

Restricts syscalls to the host’s CPU architecture (e.g., x86_64), blocking cross-architecture exploits.

3. Advanced Sandboxing Techniques

For high-security services (e.g., exposed web apps), combine basic directives with advanced sandboxing:

ProtectKernelTunables=yes

Prevents modification of kernel parameters (e.g., /proc/sys/, /sys/), blocking attacks that alter system behavior.

ProtectKernelModules=yes

Disables loading/unloading kernel modules, a common vector for rootkits.

LockPersonality=yes

Blocks changes to the process personality (e.g., switching to 32-bit mode on a 64-bit system), preventing architecture-specific exploits.

RestrictRealtime=yes

Prevents the service from using real-time scheduling, which can lead to denial-of-service (DoS) attacks.

SystemCallFilter=~@clock @cpu-emulation @debug @keyring

Explicitly block high-risk syscall groups (e.g., @debug for debugging syscalls like ptrace).

4. Auditing and Monitoring Systemd Services

Hardening isn’t set-it-and-forget-it. Use these tools to audit and monitor your services:

systemd-analyze security <service>

Generates a security score (0 = best, 10 = worst) and highlights missing hardening directives.

Example output for Nginx:

# systemd-analyze security nginx  
  NAME                          DESCRIPTION                                       EXPOSURE  
 PrivateTmp=yes               Service has private /tmp/ and /var/tmp/            0.0  
 User=                        Service runs as root                               9.0  
 CapabilityBoundingSet=       Service may have excessive capabilities             3.0  
...  
 Overall exposure level: 6.5 UNSAFE 😨  

Fix issues incrementally to lower the score (aim for ≤ 2.0).

journalctl -u <service>

Monitor service logs for errors (e.g., permission denials due to over-restrictive directives).

Filter for security-related logs:

journalctl -u nginx -g "denied|error|permission"  

auditd

Track file access, syscalls, and capability usage by the service. Add rules to /etc/audit/rules.d/:

# Monitor writes to /etc/passwd by nginx  
-a always,exit -F auid=1001 -F path=/etc/passwd -F perm=w -k nginx-passwd-write  

5. Best Practices for Maintaining Hardened Services

  • Test Changes: After modifying a service, run systemctl daemon-reload and systemctl restart <service>. Check systemctl status <service> and logs for errors.
  • Start Minimal: Begin with a strict policy (e.g., ProtectSystem=strict, NoNewPrivileges=yes) and relax only what’s needed.
  • Use systemd-analyze verify <service>: Validates the service file for syntax errors.
  • Automate: Use tools like Ansible or Puppet to enforce hardening across fleets.
  • Stay Updated: Systemd regularly adds new security features (e.g., DynamicUser= in v232+). Update your OS to get the latest directives.

6. Conclusion

Systemd isn’t just an init system—it’s a powerful security tool. By combining isolation, privilege reduction, filesystem protection, and syscall filtering, you can drastically reduce your service’s attack surface.

Start small: Pick one service (e.g., Nginx), apply the directives in this guide, and use systemd-analyze security to measure improvement. Over time, extend hardening to all critical services.

Remember: Security is a journey, not a destination. Regularly audit, test, and update your hardening policies to stay ahead of emerging threats.

7. References


Stay secure, and happy hardening!