What Is systemd?

systemd is PID 1 on most modern distros. It parallelizes boot, tracks service dependencies, collects logs via journald, mounts filesystems, and manages cgroups for resource control.

  systemctl status
ps -p 1 -o comm=
systemd --version
  

Units are defined in .service, .timer, .mount, .target, and .socket files under /usr/lib/systemd/system/ (packages) and /etc/systemd/system/ (admin overrides).

Service Lifecycle

  sudo systemctl start nginx
sudo systemctl stop nginx
sudo systemctl restart nginx       # stop then start
sudo systemctl reload nginx        # SIGHUP — reload config if supported
sudo systemctl try-reload nginx    # reload or restart if reload unsupported

sudo systemctl enable nginx        # start on boot
sudo systemctl disable nginx
sudo systemctl enable --now nginx  # enable + start in one step

systemctl is-active nginx
systemctl is-enabled nginx
systemctl is-failed nginx
  

List and filter units:

  systemctl list-units --type=service
systemctl list-units --type=service --state=running
systemctl list-units --failed
systemctl list-unit-files --type=service --state=enabled
systemctl status ssh.service
  

Boot Targets

Target Purpose
multi-user.target Multi-user CLI (typical server default)
graphical.target Desktop environment
rescue.target Single-user recovery
emergency.target Minimal shell, no services
  systemctl get-default
sudo systemctl set-default multi-user.target
sudo systemctl isolate rescue.target   # caution: drops to rescue now
  

Writing a Unit File

  # /etc/systemd/system/myapp.service
[Unit]
Description=My Application API
Documentation=https://docs.example.com/myapp
After=network-online.target postgresql.service
Wants=network-online.target
Requires=postgresql.service

[Service]
Type=simple
User=myapp
Group=myapp
WorkingDirectory=/opt/myapp
Environment=NODE_ENV=production
ExecStart=/opt/myapp/bin/server --port 8080
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=5
LimitNOFILE=65535
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target
  

Deploy and verify:

  sudo systemctl daemon-reload
sudo systemctl enable --now myapp.service
systemctl cat myapp.service          # show effective unit (with overrides)
journalctl -u myapp -f
  

Service Types

Type Use when
simple Process stays in foreground (default)
forking Daemon double-forks (legacy)
oneshot Runs once and exits (setup tasks)
notify Process sends sd_notify when ready

Drop-In Overrides

Prefer overrides over editing vendor units:

  sudo systemctl edit myapp.service
# Creates /etc/systemd/system/myapp.service.d/override.conf

# Example override content:
# [Service]
# Environment=LOG_LEVEL=debug
sudo systemctl daemon-reload
sudo systemctl restart myapp
  

Timers (cron Alternative)

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

[Timer]
OnCalendar=daily
Persistent=true
RandomizedDelaySec=300

[Install]
WantedBy=timers.target
  

Pair with backup.service (Type=oneshot), then:

  sudo systemctl enable --now backup.timer
systemctl list-timers --all
  

Timers offer logging via journal, dependency awareness, and missed-run catch-up (Persistent=true).

Journal Logs

  journalctl -xe                     # recent errors with hints
journalctl -u nginx --since today
journalctl -u nginx --since "2026-06-01 08:00" --until "2026-06-01 09:00"
journalctl -p err -b               # errors since last boot
journalctl -f                      # follow all logs
journalctl -k                      # kernel messages

# Disk usage
journalctl --disk-usage
sudo journalctl --vacuum-size=500M
  

Configure retention in /etc/systemd/journald.conf (SystemMaxUse=, MaxRetentionSec=).

Troubleshooting Failed Units

  systemctl status myapp.service -l --no-pager
journalctl -u myapp -n 100 --no-pager
systemctl reset-failed              # clear failed state after fix

# Boot performance
systemd-analyze
systemd-analyze blame               # slow-starting units
systemd-analyze critical-chain myapp.service
  

Common failures: wrong User, missing ExecStart path, port already in use, missing daemon-reload after edit.

Best Practices

Practice Reason
Always daemon-reload after unit changes systemd caches unit definitions
Use Restart=on-failure Auto-recover from crashes
Set LimitNOFILE and MemoryMax Prevent resource exhaustion
Log to journal (StandardOutput=journal) Centralized querying with journalctl
Test with systemctl start before enable Avoid boot loops

Common Mistakes

Mistake Consequence
Editing files in /usr/lib/systemd/ Overwritten on package upgrade
Forgetting User= directive Service runs as root
Type=forking with wrong PIDFile systemd thinks service failed
No After=network-online.target Service starts before network ready

Production Scenario

A Node.js API crashes under memory pressure:

  # override.conf
[Service]
MemoryMax=1G
MemoryHigh=800M
Restart=on-failure
RestartSec=10
StartLimitBurst=5
StartLimitIntervalSec=300
  

Combined with alerting on systemctl is-failed myapp and journalctl -p err -u myapp, ops gets paged before users notice repeated restart loops.

systemd replaces ad-hoc init scripts with declarative units — master systemctl, journalctl, and unit file anatomy and you control server behavior from boot through shutdown.