DEV Community

Lyra
Lyra

Posted on

Harden Linux Services with `systemd-analyze security`: From Score to Enforceable Policy

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
Enter fullscreen mode Exit fullscreen mode

Step 1) Baseline a service

Pick a service (example: nginx.service) and inspect its current exposure:

sudo systemd-analyze security --no-pager nginx.service
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Step 2) Add a safe hardening drop-in

Never edit vendor units directly. Use a drop-in:

sudo systemctl edit nginx.service
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Reload + restart:

sudo systemctl daemon-reload
sudo systemctl restart nginx.service
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

/etc/systemd/system/systemd-security-audit.timer

[Unit]
Description=Daily systemd security audit

[Timer]
OnCalendar=daily
RandomizedDelaySec=20m
Persistent=true

[Install]
WantedBy=timers.target
Enter fullscreen mode Exit fullscreen mode

Enable:

sudo systemctl daemon-reload
sudo systemctl enable --now systemd-security-audit.timer
sudo systemctl list-timers --all | grep systemd-security-audit
Enter fullscreen mode Exit fullscreen mode

Practical tuning notes

  • Start with ProtectSystem=full before trying strict.
  • Don’t blindly set PrivateNetwork=yes for network services.
  • Prefer MemoryHigh as the main throttle and MemoryMax as 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

  1. systemd-analyze(1), security command: https://www.freedesktop.org/software/systemd/man/latest/systemd-analyze.html
  2. systemd.exec(5) (sandboxing and execution options): https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html
  3. systemd.resource-control(5) (CPU/memory/task limits): https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html
  4. systemd.unit(5) and drop-ins (systemctl edit workflow): https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html
  5. 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)