A security-focused guide to using systemctl for Linux service management — covering service auditing, disabling unnecessary services, applying systemd sandboxing directives, and reducing the attack surface of production servers.
What is systemctl and Why Does It Matter for Security?
systemctl is the primary command-line interface to systemd, the init system and service manager used on virtually all modern Linux distributions. From a security perspective, systemctl is the authoritative tool for auditing which services are running, which are enabled to start at boot, and for applying service-level sandboxing that limits what a compromised service can do to the rest of the system. Every running service that is unnecessary represents unrealized attack surface — a potential entry point for exploitation, privilege escalation, or lateral movement. systemd also provides powerful security directives that can be applied per-unit to restrict capabilities, filesystem access, network access, and system calls — a form of built-in application containment that does not require SELinux or AppArmor expertise.
Syntax
systemctl list-units --type=service --state=running
systemctl list-units --type=service --state=enabled
systemctl list-unit-files --type=service
systemctl status <service>
systemctl disable <service>
systemctl stop <service> && systemctl disable <service>
systemctl mask <service>
systemctl show <service> | grep -i security
systemd-analyze security <service>list-units shows active units filtered by type and state. list-unit-files shows all installed unit files and their enabled/disabled status. mask is stronger than disable — it creates a symlink to /dev/null that prevents the unit from being started manually or as a dependency. systemd-analyze security provides a security exposure score for a given service unit — the single most useful command for systematically hardening services.
Key Options and Flags
| Flag / Parameter | Description | Security Note |
|---|---|---|
list-units --type=service --state=running | List all currently active (running) service units | Starting point for any service audit — verify every running service is intentional |
list-unit-files --type=service | List all installed service unit files and their enabled/disabled/masked state | Reveals services enabled to start at boot that may not currently be running |
disable <service> | Prevent a service from starting automatically at boot | Does not stop a currently running service — follow with stop for immediate effect |
mask <service> | Completely block a service — symlinks unit file to /dev/null, preventing any start | Use for services you never want to run — stronger than disable, cannot be started as a dependency |
stop <service> | Immediately stop a running service | — |
status <service> | Show current state, recent log output, and PID of a service | Reveals the UID/GID the service runs as — services running as root when not required are a hardening target |
show <service> | Show all properties of a service unit in machine-readable format | Pipe to grep -i security to see applied security directives |
systemd-analyze security <service> | Score the security exposure of a unit file against systemd's built-in hardening checklist | Provides per-directive recommendations — use to prioritize hardening work |
Common Use Cases
Audit All Running Services and Identify Unnecessary Attack Surface
The first step in service hardening is establishing what is running and why. On a minimal production server, the list of running services should be short and every entry should have a documented reason. Services like Bluetooth, CUPS (printing), Avahi (mDNS discovery), and ModemManager are never required on a headless server and represent pure attack surface.
# List all running services
systemctl list-units --type=service --state=running
# List all enabled services (start at boot)
systemctl list-unit-files --type=service | grep enabled
# Common unnecessary services on server installs
for svc in bluetooth cups avahi-daemon ModemManager rpcbind; do
systemctl is-active $svc 2>/dev/null && echo "RUNNING (disable): $svc"
doneDisable and Mask Unnecessary Services
Disabling a service prevents it from starting at boot. Masking goes further — it makes the service impossible to start by any means, including as a dependency of another unit. For services you are certain will never be needed (e.g., Bluetooth on a data center server), masking is the appropriate action.
# Stop and disable a service
systemctl stop bluetooth
systemctl disable bluetooth
# Mask services that should never run
systemctl mask bluetooth cups avahi-daemon ModemManager
# Verify masked status
systemctl status bluetooth
# Output should show: Loaded: masked (/dev/null; masked)Score and Harden a Service Unit with systemd-analyze
systemd-analyze security provides a scored assessment of a service unit against systemd’s built-in security directives. A lower score means better hardening. This command identifies exactly which sandboxing options are missing, making it a practical guide for hardening any service — especially custom application units.
# Score the security exposure of nginx
systemd-analyze security nginx
# Example output (abridged):
# PrivateTmp= UNSAFE Service has access to other services' temporary files
# NoNewPrivileges= UNSAFE Service processes may acquire new privileges
# ProtectSystem= UNSAFE Service has write access to the OS file hierarchy
# ...
# -> Overall exposure level for nginx.service: 9.6 UNSAFE
# After adding hardening directives to the unit file:
systemd-analyze security nginx # Re-score to verify improvementSecurity Relevance: Attack Surface Reduction and Service Sandboxing
Every running service that is not required is a potential vector for exploitation. Services that listen on network sockets — even on localhost — represent remote attack surface if the network configuration changes or if another compromised process can reach them. systemd’s security directives provide a powerful, built-in mechanism for limiting what a compromised service can do: PrivateTmp=yes gives the service an isolated /tmp, preventing access to other services’ temporary files. NoNewPrivileges=yes prevents the service from gaining additional capabilities via setuid binaries. ProtectSystem=strict mounts the OS filesystem read-only for the service. PrivateNetwork=yes removes all network access. CapabilityBoundingSet= restricts which Linux capabilities the service may use. These directives do not require kernel module configuration — they are applied entirely in the unit file and take effect on the next service restart.
- Masking a service with systemctl mask cannot be undone with enable — use systemctl unmask explicitly
- systemctl disable removes symlinks but does not stop the service if it is currently running — always follow with stop
- Some services have alias names — verify with systemctl list-unit-files | grep <name> to find all enabled variants
- Adding ProtectSystem=strict or ReadOnlyPaths to a service may break it if it writes to filesystem locations you have not whitelisted — test in staging first
- A service running as root that does not require root privileges is a privilege escalation vector — add User= and Group= directives to run it as a dedicated service account
Practical Examples
Generate a full service security audit report
echo '=== Running Services ===' > /tmp/service_audit.txt
systemctl list-units --type=service --state=running --no-pager >> /tmp/service_audit.txt
echo '\n=== Enabled at Boot ===' >> /tmp/service_audit.txt
systemctl list-unit-files --type=service | grep enabled >> /tmp/service_audit.txt
echo '\n=== Services Running as Root ===' >> /tmp/service_audit.txt
systemctl list-units --type=service --state=running --no-pager | awk '{print $1}' | \
xargs -I{} sh -c 'u=$(systemctl show {} -p User --value); [ -z "$u" ] && echo "{} (root/default)"' \
>> /tmp/service_audit.txt 2>/dev/null
cat /tmp/service_audit.txtGenerates a comprehensive service audit file documenting all running services, boot-enabled services, and services running without an explicit User= directive (which default to root). Review this output against your expected service list and investigate any unexpected entries.
Apply systemd security hardening to an nginx unit
# Create an override file (preferred over editing the package unit file directly)
mkdir -p /etc/systemd/system/nginx.service.d/
cat > /etc/systemd/system/nginx.service.d/hardening.conf << 'EOF'
[Service]
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict
ProtectHome=yes
ReadWritePaths=/var/log/nginx /var/lib/nginx
CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_SETUID CAP_SETGID
EOF
systemctl daemon-reload
systemctl restart nginx
systemd-analyze security nginx # Verify score improvedAlways use drop-in override files in /etc/systemd/system/
Find and investigate services listening on unexpected network ports
# List all services with open sockets
systemctl list-units --type=service --state=running --no-pager | \
awk 'NR>1 && NF {print $1}' | \
xargs -I{} sh -c 'ss -tlnp 2>/dev/null | grep -q $(systemctl show {} -p MainPID --value) && echo "Network: {}"' 2>/dev/null
# Cross-reference with ss output
ss -tlnp
ss -ulnp # UDP listenersCross-referencing running services with their network socket exposure identifies services with unexpected listeners. A service you believe is internal-only but has a socket on 0.0.0.0 is externally exposed. Use this to verify services are listening only on intended addresses.
Troubleshooting Common Issues
Problem: systemctl disable has no effect — service still starts at boot
Solution: Check for alias unit names: systemctl list-unit-files | grep . Some services have multiple unit names; disabling one alias may not disable all. Also check if the service is enabled via a vendor preset or a socket unit: systemctl list-unit-files | grep socket. Socket-activated services start when a connection arrives even if the service unit is disabled.
Problem: systemd-analyze security is not available
Solution: systemd-analyze security requires systemd version 246 or later. Check with systemctl --version. On older systems (e.g., Ubuntu 18.04 with systemd 237), this subcommand does not exist. Upgrade to a current LTS release or manually review the security directives documented in the systemd.exec man page.
Problem: Service fails to start after adding hardening directives
Solution: Check the service journal immediately after the failure: journalctl -xe -u . The most common causes are: ProtectSystem=strict blocking writes to a path not in ReadWritePaths=, PrivateTmp=yes breaking services that share data via /tmp, or CapabilityBoundingSet= removing a capability the service requires. Add the required path to ReadWritePaths= or adjust the capability set, then reload and restart.
Summary
systemctl is not just a service management tool — it is a primary security hardening interface for Linux systems. The combination of auditing running services to eliminate unnecessary attack surface and applying systemd’s built-in sandboxing directives to the services that remain represents one of the highest-return hardening activities available on modern Linux. Use systemd-analyze security to score and prioritize hardening work, apply directives via drop-in override files to survive package updates, and mask services that should never run rather than simply disabling them.
- Run
systemctl list-units --type=service --state=runningand verify every service has a documented reason to exist — disable and mask everything else - Use
systemd-analyze securityto get a scored, directive-level hardening roadmap for every service that must run - Apply hardening via drop-in files in
/etc/systemd/system/— changes survive package updates and can be version-controlled.d/
Related Commands
lynis for comprehensive security auditing • ss to audit network socket exposure • journalctl for systemd service log analysis • crontab audit for scheduled task security review
Are Your Linux Services Hardened and Audited?
INTRAM provides managed Linux server hardening including systematic service audits, unnecessary service removal, and systemd sandboxing configuration for all running services.
Request Server Hardening