A

systemd Hardening: Sandboxing Services with Security Directives

A
Amit Nepal
Security Engineer · Linux & Infrastructure · Offensive Security
·Mar 18, 2025·1 min read
Linux

systemd Hardening: Sandboxing Services with Security Directives

Mar 18, 2025 · 1 min read

The Problem with Default Service Permissions

Most systemd services run with more privilege than they need. A compromised nginx process shouldn't be able to read /etc/ssh/ssh_host_rsa_key or write to /home. systemd gives us the tools to enforce this at the unit level — no kernel patches required.

Key Directives

[Service]
# Run as non-root user
User=www-data
Group=www-data

# Filesystem isolation
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/myapp

# Capabilities
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_BIND_SERVICE
NoNewPrivileges=true

# Syscall filtering
SystemCallFilter=@system-service
SystemCallErrorNumber=EPERM

# Network namespace (optional)
PrivateNetwork=false
IPAddressAllow=localhost

Checking Your Score

systemd-analyze security nginx.service

The output gives a numeric security exposure score (lower is better, 0-10 scale). Stock nginx on Ubuntu scores around 9.6. With the directives above you can get under 3.

Iterative Hardening

Don't add all directives at once. Add one, restart, watch journalctl -u nginx -f for permission errors, adjust, repeat. The PrivateTmp=true directive is always safe and free hardening.

Defensive Takeaways

  • Run systemd-analyze security on every custom unit you write
  • NoNewPrivileges=true should be default for every service
  • ProtectKernelModules=true prevents a compromised service from loading rootkit modules
  • Combine with SELinux/AppArmor for defence in depth
Keep going

Get the next writeup in your inbox

New posts delivered when I publish. No spam.