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 callssd_notifywhen 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
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=alwayswithout 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 .