Harden Linux Services with systemd-analyze security: From Score to Enforceable Policy
If you run long-lived services on Linux, hardening often gets postponed because it feels risky: "What if I break production?"
A practical way to avoid guesswork is to use systemd-analyze security as a baseline, then apply hardening in small, testable drop-ins.
This guide walks through a repeatable workflow you can use on Debian/Ubuntu/Fedora/Arch hosts that run systemd.
Why this works
systemd-analyze security evaluates a unit against sandboxing and privilege-related settings and reports an exposure score with detailed findings. It helps you prioritize what to harden first.
Important: this score is a heuristic, not a proof of safety. Use it to guide improvements, then validate behavior with service-level tests.
Prerequisites
- Linux host with systemd
- Root or sudo access
- A non-critical service to practice on first
Check version:
systemd --version
systemd-analyze --version
Step 1) Baseline a service
Pick a service (example: nginx.service) and inspect its current exposure:
sudo systemd-analyze security --no-pager nginx.service
Capture the output so you can compare before/after:
sudo mkdir -p /var/log/systemd-hardening
sudo systemd-analyze security --no-pager nginx.service \
| sudo tee /var/log/systemd-hardening/nginx.before.txt
Step 2) Add a safe hardening drop-in
Never edit vendor units directly. Use a drop-in:
sudo systemctl edit nginx.service
Paste:
[Service]
# Privilege/sandbox basics
NoNewPrivileges=yes
PrivateTmp=yes
PrivateDevices=yes
ProtectSystem=full
ProtectHome=read-only
ProtectControlGroups=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
LockPersonality=yes
RestrictSUIDSGID=yes
# Networking/process constraints (keep AF_INET/AF_INET6 if web-facing)
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
RestrictNamespaces=yes
# Resource guardrails (tune for your workload)
CPUQuota=80%
MemoryHigh=400M
MemoryMax=600M
TasksMax=512
Reload + restart:
sudo systemctl daemon-reload
sudo systemctl restart nginx.service
Step 3) Validate functionality (donβt skip)
sudo systemctl --no-pager --full status nginx.service
sudo journalctl -u nginx.service -n 100 --no-pager
curl -I http://127.0.0.1
If something breaks, revert quickly:
sudo rm -f /etc/systemd/system/nginx.service.d/override.conf
sudo systemctl daemon-reload
sudo systemctl restart nginx.service
Step 4) Re-score and compare
sudo systemd-analyze security --no-pager nginx.service \
| sudo tee /var/log/systemd-hardening/nginx.after.txt
diff -u /var/log/systemd-hardening/nginx.before.txt \
/var/log/systemd-hardening/nginx.after.txt || true
You should see fewer risky findings and a lower exposure score.
Step 5) Automate checks across multiple units
Create /usr/local/sbin/systemd-security-audit.sh:
#!/usr/bin/env bash
set -euo pipefail
OUT_DIR="/var/log/systemd-hardening"
mkdir -p "$OUT_DIR"
UNITS=(
nginx.service
ssh.service
docker.service
)
for u in "${UNITS[@]}"; do
echo "==> Auditing $u"
if systemctl list-unit-files --type=service | awk '{print $1}' | grep -qx "$u"; then
TS="$(date -u +%Y%m%dT%H%M%SZ)"
systemd-analyze security --no-pager "$u" > "$OUT_DIR/${u}.${TS}.txt" || true
else
echo "WARN: unit not found: $u"
fi
done
Make executable and run:
sudo install -m 0755 /usr/local/sbin/systemd-security-audit.sh /usr/local/sbin/systemd-security-audit.sh
sudo /usr/local/sbin/systemd-security-audit.sh
Optional timer to run daily:
/etc/systemd/system/systemd-security-audit.service
[Unit]
Description=Run systemd security audit for selected services
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/systemd-security-audit.sh
/etc/systemd/system/systemd-security-audit.timer
[Unit]
Description=Daily systemd security audit
[Timer]
OnCalendar=daily
RandomizedDelaySec=20m
Persistent=true
[Install]
WantedBy=timers.target
Enable:
sudo systemctl daemon-reload
sudo systemctl enable --now systemd-security-audit.timer
sudo systemctl list-timers --all | grep systemd-security-audit
Practical tuning notes
- Start with
ProtectSystem=fullbefore tryingstrict. - Donβt blindly set
PrivateNetwork=yesfor network services. - Prefer
MemoryHighas the main throttle andMemoryMaxas the hard limit. - Hardening is service-specific: apply incrementally, validate each change, and keep rollback steps ready.
Suggested tags
linux, systemd, security, opensource
Cover image (Unsplash)
https://images.unsplash.com/photo-1510511459019-5dda7724fd87?auto=format&fit=crop&w=1400&q=80
References
- systemd-analyze(1),
securitycommand: https://www.freedesktop.org/software/systemd/man/latest/systemd-analyze.html - systemd.exec(5) (sandboxing and execution options): https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html
- systemd.resource-control(5) (CPU/memory/task limits): https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html
- systemd.unit(5) and drop-ins (
systemctl editworkflow): https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html - Red Hat docs on cgroups v2 + systemd resource controls (implementation context): https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/8/html/managing_monitoring_and_updating_the_kernel/assembly_configuring-resource-management-using-systemd_managing-monitoring-and-updating-the-kernel
Top comments (0)