Linux Systemd Guide
Units, services, targets, timers, socket activation, journald, and cgroups v2 integration
systemd (PID 1) │ ┌────────────┼────────────┐ │ │ │ .service .timer .socket (daemon) (cron-like) (on-demand) │ │ │ └────────────┼────────────┘ │ cgroups v2 slice ┌──────┴──────┐ system.slice user.slice │ nginx.service → /sys/fs/cgroup/system.slice/nginx.service/ ├─ memory.max ├─ cpu.max └─ cgroup.procs
systemctlCore

systemctl is the primary interface to systemd for managing units, checking status, and querying the system state.

Shell
# Start / stop / restart / reload
systemctl start nginx
systemctl stop nginx
systemctl restart nginx
systemctl reload nginx          # sends SIGHUP (if supported)

# Enable / disable at boot
systemctl enable nginx
systemctl disable nginx
systemctl enable --now nginx    # enable + start immediately

# Mask / unmask (prevent any start)
systemctl mask nginx
systemctl unmask nginx

# Status and logs
systemctl status nginx          # state, PID, recent journal output
systemctl is-active nginx
systemctl is-enabled nginx
systemctl is-failed nginx

# List units
systemctl list-units --type=service
systemctl list-units --failed
systemctl list-unit-files --type=service

# Reload systemd configuration (after editing unit files)
systemctl daemon-reload
Service UnitsUnits

Service units define how systemd starts and supervises a process. They live in /etc/systemd/system/ (admin) or /lib/systemd/system/ (packages). Drop-in overrides go in /etc/systemd/system/name.service.d/*.conf.

Full annotated service unit

INI
[Unit]
Description=My Application
After=network.target postgresql.service   # ordering
Requires=postgresql.service               # hard dependency
Wants=redis.service                       # soft dependency

[Service]
Type=simple                   # simple|forking|notify|oneshot|idle
User=myapp
Group=myapp
WorkingDirectory=/opt/myapp
Environment=APP_ENV=production
EnvironmentFile=/etc/myapp/env

ExecStartPre=/opt/myapp/bin/migrate
ExecStart=/opt/myapp/bin/server --port 8080
ExecReload=/bin/kill -HUP $MAINPID
ExecStop=/bin/kill -TERM $MAINPID

Restart=on-failure            # always|on-failure|on-abnormal|no
RestartSec=5s
StartLimitBurst=3
StartLimitIntervalSec=30s

# Resource limits
LimitNOFILE=65536
MemoryMax=1G
CPUQuota=200%                 # 200% = 2 full CPUs

# Sandboxing
PrivateTmp=yes                # isolated /tmp
ProtectSystem=strict          # read-only /usr /boot /etc
ProtectHome=yes               # no access to /home /root
NoNewPrivileges=yes           # prevent privilege escalation
ReadWritePaths=/var/lib/myapp

[Install]
WantedBy=multi-user.target

Service types

TypeBehaviour
simpleExecStart process is the main service. systemd considers it started immediately.
forkingExecStart forks and exits. Parent exit = service started. Use PIDFile= to track main process.
notifyProcess calls sd_notify(READY=1) when ready. Most reliable — systemd knows exactly when started.
oneshotShort-lived task. systemd waits for it to exit before considering it done. Use RemainAfterExit=yes if appropriate.
dbusService is ready when it acquires a D-Bus name. Use BusName=.

Override a vendor unit (drop-in)

Shell
# Create a drop-in to override a specific setting
systemctl edit nginx           # opens editor, creates drop-in automatically

# Or manually:
mkdir -p /etc/systemd/system/nginx.service.d/
cat > /etc/systemd/system/nginx.service.d/override.conf <<'EOF'
[Service]
LimitNOFILE=65536
EOF

systemctl daemon-reload
systemctl restart nginx
TargetsBoot

Targets group units and define system states. They replace SysV runlevels. The default target determines what gets started at boot.

Common targets

TargetSysV equivDescription
poweroff.target0System shutdown
rescue.target1Single-user mode, minimal services, root shell
multi-user.target3Multi-user, no GUI — typical server default
graphical.target5Multi-user with GUI
reboot.target6Reboot
emergency.targetMinimal shell, root filesystem read-only
network.targetNetwork configured (not necessarily up)
network-online.targetNetwork fully online — use Wants= sparingly
Shell
# Show current target
systemctl get-default

# Change default target
systemctl set-default multi-user.target

# Switch to a target immediately (without reboot)
systemctl isolate rescue.target

# Reboot / shutdown
systemctl reboot
systemctl poweroff
systemctl suspend
TimersUnits

Systemd timers replace cron. Each timer activates a corresponding .service unit. They support monotonic (relative to boot/last run) and realtime (calendar-based) schedules, missed run tracking, and can be inspected with systemctl.

Timer unit types

INI
# /etc/systemd/system/backup.timer
[Unit]
Description=Daily backup timer

[Timer]
# Realtime (calendar) — run every day at 02:30
OnCalendar=*-*-* 02:30:00
# Catch up if system was off when scheduled
Persistent=true

# Monotonic — run 15 minutes after boot, then every hour
# OnBootSec=15min
# OnUnitActiveSec=1h

# Random spread to avoid thundering herd
RandomizedDelaySec=5min
Unit=backup.service

[Install]
WantedBy=timers.target
Shell
# List all timers with next/last trigger
systemctl list-timers --all

# Enable and start timer
systemctl enable --now backup.timer

# Test the timer's service immediately
systemctl start backup.service

# OnCalendar examples
# hourly            → *-*-* *:00:00
# daily             → *-*-* 00:00:00
# weekly            → Mon *-*-* 00:00:00
# Mon..Fri 09:00    → Mon..Fri *-*-* 09:00:00

# Verify calendar expression
systemd-analyze calendar "Mon..Fri *-*-* 09:00:00"
Socket ActivationActivation

Socket activation starts a service on-demand when the first connection arrives. systemd holds the socket open; the service inherits it on start via file descriptor 3 (or via sd_listen_fds()). This enables parallelised boot and zero-downtime restarts.

INI
# /etc/systemd/system/myapp.socket
[Unit]
Description=My App Socket

[Socket]
ListenStream=8080        # TCP port
# ListenStream=/run/myapp.sock   # Unix socket
Accept=no                # one instance handles all connections
# Accept=yes             # fork a new instance per connection

[Install]
WantedBy=sockets.target
---
# /etc/systemd/system/myapp.service
[Unit]
Description=My App

[Service]
ExecStart=/opt/myapp/bin/server
# Service receives the socket on fd 3 via sd_listen_fds()
Shell
# Enable socket (not the service — it starts on demand)
systemctl enable --now myapp.socket

# Zero-downtime restart: systemd keeps socket open
systemctl restart myapp.service   # new process inherits socket
journaldLogging

journald collects logs from kernel, systemd units, and any process writing to stdout/stderr. Logs are stored in a structured binary format and queried with journalctl.

journalctl filtering

Shell
# Follow logs for a service (like tail -f)
journalctl -fu nginx

# Last 100 lines
journalctl -n 100 -u nginx

# Since a time / between times
journalctl -u nginx --since "2026-05-13 10:00" --until "2026-05-13 11:00"
journalctl -u nginx --since "1 hour ago"

# By priority (0=emerg .. 7=debug)
journalctl -p err          # err and above
journalctl -p warning..err

# Kernel messages (like dmesg)
journalctl -k
journalctl -k --since "today"

# By PID or UID
journalctl _PID=1234
journalctl _UID=1000

# JSON output for structured processing
journalctl -u nginx -o json | jq .MESSAGE

# Show disk usage
journalctl --disk-usage

# Vacuum old logs
journalctl --vacuum-time=30d
journalctl --vacuum-size=500M

Persistent journal configuration

INI
# /etc/systemd/journald.conf
[Journal]
Storage=persistent          # auto|volatile|persistent|none
Compress=yes
SystemMaxUse=2G
SystemKeepFree=500M
MaxRetentionSec=1month
ForwardToSyslog=no          # set yes to also forward to rsyslog
Enable persistent journal: mkdir -p /var/log/journal && systemctl restart systemd-journald. Without this, logs are lost on reboot.
cgroups IntegrationResources

Every systemd service runs in its own cgroup slice. This provides isolation, resource limits, and accurate accounting per service.

Shell
# Show cgroup tree
systemd-cgls

# Live resource usage per service
systemd-cgtop

# Set resource limits at runtime (transient, lost on restart)
systemctl set-property nginx.service MemoryMax=512M
systemctl set-property nginx.service CPUQuota=50%

# Make permanent (writes to override file)
systemctl set-property --runtime=false nginx.service MemoryMax=512M

# Available service resource properties
# MemoryMax=, MemoryHigh=, MemorySwapMax=
# CPUQuota=, CPUWeight=
# IOWeight=, IOReadBandwidthMax=, IOWriteBandwidthMax=
# TasksMax=
# LimitNOFILE=, LimitNPROC=
Boot AnalysisDebug
Shell
# Total boot time summary
systemd-analyze

# Time per unit (slowest first)
systemd-analyze blame

# Show critical chain (longest path)
systemd-analyze critical-chain

# SVG timeline of all units
systemd-analyze plot > boot.svg

# Check unit file syntax
systemd-analyze verify /etc/systemd/system/myapp.service

# Security score for a service
systemd-analyze security nginx.service

# Show unit dependencies
systemctl list-dependencies nginx
systemctl list-dependencies --reverse nginx  # what depends on it
Cheat SheetReference

systemctl

systemctl enable --now svc
systemctl restart svc
systemctl daemon-reload
systemctl list-units --failed

journalctl

journalctl -fu svc
journalctl -p err --since today
journalctl -k — kernel
journalctl --vacuum-time=30d

Service unit keys

Type=notify — most reliable
Restart=on-failure
PrivateTmp=yes
NoNewPrivileges=yes

Timers

OnCalendar=daily
Persistent=true
systemctl list-timers
systemd-analyze calendar "..."

Boot debug

systemd-analyze blame
systemd-analyze critical-chain
systemd-analyze security svc
systemd-analyze verify unit

cgroups

systemd-cgls
systemd-cgtop
systemctl set-property svc MemoryMax=1G
systemctl set-property svc CPUQuota=50%