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

Published: (March 5, 2026 at 12:14 AM EST)
3 min read
Source: Dev.to

Source: Dev.to

Introduction

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.

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: the 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 your versions:

systemd --version
systemd-analyze --version

Inspect the Current Exposure

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

Create a Drop‑In

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

sudo systemctl edit nginx.service

Paste the following into the editor that opens:

[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 and Test

sudo systemctl daemon-reload
sudo systemctl restart nginx.service

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

Compare Before/After

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.

Automate Audits

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 it 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 Daily Timer

Create the service unit /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

Create the timer unit /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 and start the timer:

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

Practical Tips

  • Start with ProtectSystem=full before trying stricter settings.
  • 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.

References

  • Systemd‑analyze(1) – security command
  • systemd.exec(5) – sandboxing and execution options
  • systemd.resource-control(5) – CPU/memory/task limits
  • systemd.unit(5) and drop‑ins (systemctl edit workflow)
  • Red Hat docs on cgroups v2 + systemd resource controls

Linux systemd

0 views
Back to Blog

Related posts

Read more »