systemd cheatsheet.

systemctl basics

systemctl status nginx
systemctl start nginx
systemctl stop nginx
systemctl restart nginx
systemctl reload nginx           # if supported
systemctl enable nginx           # start on boot
systemctl disable nginx
systemctl enable --now nginx     # enable + start
systemctl is-active nginx
systemctl is-enabled nginx
systemctl is-failed nginx

List

systemctl list-units
systemctl list-units --type=service
systemctl list-units --state=failed
systemctl list-unit-files
systemctl list-timers

Service file

# /etc/systemd/system/myapp.service
[Unit]
Description=My App
After=network-online.target postgresql.service
Wants=network-online.target
Requires=postgresql.service

[Service]
Type=simple
User=myapp
Group=myapp
WorkingDirectory=/opt/myapp
EnvironmentFile=/etc/myapp/env
ExecStart=/opt/myapp/venv/bin/gunicorn config.wsgi
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=5
TimeoutStopSec=30
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target
systemctl daemon-reload
systemctl enable --now myapp

Type

  • simple: default, ExecStart runs in foreground.
  • forking: ExecStart forks; daemon-style.
  • oneshot: runs once, exits.
  • notify: app calls sd_notify when ready.
  • idle: like simple but delayed.

Restart policies

no
on-success
on-failure
on-abnormal
on-watchdog
on-abort
always

Environment

Environment="VAR=value"
EnvironmentFile=/etc/myapp/env
EnvironmentFile=-/etc/myapp/env.optional    # - means optional

Drop-in overrides

systemctl edit nginx              # creates override.conf
# /etc/systemd/system/nginx.service.d/override.conf
[Service]
LimitNOFILE=65536
systemctl daemon-reload
systemctl restart nginx

Survives package upgrades.

journalctl

journalctl                                # all
journalctl -u nginx
journalctl -u nginx -f                    # follow
journalctl -u nginx --since "1 hour ago"
journalctl -u nginx --since "2026-01-01" --until "2026-01-02"
journalctl -p err                         # priority
journalctl -k                             # kernel
journalctl --boot                         # this boot
journalctl --list-boots
journalctl --boot -1                      # previous boot
journalctl -o json
journalctl -o cat                         # just messages
journalctl --disk-usage

Set journal size

journalctl --vacuum-size=500M
journalctl --vacuum-time=7d

/etc/systemd/journald.conf:

SystemMaxUse=500M
MaxRetentionSec=1month

Timers (cron alternative)

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

[Timer]
OnCalendar=daily
OnCalendar=*-*-* 02:00:00
Persistent=true                  # run if missed
RandomizedDelaySec=600

[Install]
WantedBy=timers.target
# /etc/systemd/system/backup.service
[Unit]
Description=Backup job

[Service]
Type=oneshot
ExecStart=/opt/backup.sh
systemctl enable --now backup.timer
systemctl list-timers

OnCalendar examples

*-*-* 04:30:00              # daily 04:30
Mon..Fri *-*-* 09:00:00     # weekdays 09:00
*-*-1 03:00:00              # first of month
hourly, daily, weekly, monthly, yearly

Targets (runlevels)

systemctl get-default
systemctl set-default multi-user.target
systemctl isolate rescue.target

Sandboxing (security)

[Service]
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
PrivateDevices=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes
RestrictNamespaces=yes
RestrictRealtime=yes
SystemCallArchitectures=native
SystemCallFilter=@system-service

Lock down service. systemd-analyze security rates safety.

Resource limits

[Service]
MemoryMax=512M
MemoryHigh=400M
CPUQuota=50%
TasksMax=100
LimitNOFILE=65536

Logs from service

journalctl -u myapp -f
journalctl _SYSTEMD_UNIT=myapp.service
journalctl -p err -u myapp

Run user services

~/.config/systemd/user/myapp.service:

[Service]
ExecStart=/home/me/bin/myapp

[Install]
WantedBy=default.target
systemctl --user enable --now myapp
loginctl enable-linger              # services survive logout

Watch file changes (path units)

# foo.path
[Path]
PathChanged=/var/spool/incoming
Unit=foo.service

[Install]
WantedBy=multi-user.target

Reload after edit

systemctl daemon-reload

Edit→reload→restart pattern.

systemd-analyze

systemd-analyze blame                 # what slow at boot
systemd-analyze critical-chain
systemd-analyze plot > boot.svg
systemd-analyze verify myapp.service
systemd-analyze security myapp

socket activation

# foo.socket
[Socket]
ListenStream=8080
Accept=no

[Install]
WantedBy=sockets.target

systemd opens socket, hands to service on first request.

Common mistakes

  • Editing unit file in /lib/systemd/ (package owns; use /etc/systemd/).
  • Forgetting systemctl daemon-reload.
  • Restart=always without backoff on broken service → restart storm.
  • Logging to file inside service → bypasses journal.
  • Hard-coding paths instead of WorkingDirectory.

Read this next

If you want my service templates, they’re at rajpoot.dev .


Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .