systemd service unit basics
I keep needing to look up the difference between Wants= and
Requires=, or which Type= to use for a given daemon.
Writing it down.
Unit file locations
/usr/lib/systemd/system/— package-provided units (don't edit)/etc/systemd/system/— local overrides and custom units/etc/systemd/system/foo.service.d/override.conf— drop-in overrides for an existing unit (preferred over editing package files)
To create a drop-in without editing by hand: systemctl edit foo.service
Unit file structure
A service unit has three sections. Not all are required.
[Unit]
[Unit]
Description=My background service
Documentation=https://example.com/docs
After=network-online.target
Wants=network-online.target
After= — ordering only. This unit starts after the listed ones, but doesn't require them to succeed.
Wants= — soft dependency. systemd will try to start the listed units, but if they fail, this unit still starts.
Requires= — hard dependency. If the required unit fails or stops, this unit is also stopped. Use sparingly.
[Service]
[Service]
Type=simple
User=myuser
WorkingDirectory=/opt/myapp
ExecStart=/opt/myapp/bin/myapp --config /etc/myapp/config.yaml
ExecStop=/bin/kill -TERM $MAINPID
Restart=on-failure
RestartSec=5s
StandardOutput=journal
StandardError=journal
Type= — how systemd tracks readiness:
simple— process starts, systemd considers it ready immediately. Default for most things.exec— like simple but waits until the binary has actually been exec'd. Safer for scripted wrappers.forking— old-style daemon that forks and exits the parent. NeedsPIDFile=.oneshot— runs, exits, done. systemd waits for it to finish. Good for setup scripts andRemainAfterExit=yes.notify— daemon signals readiness viasd_notify(). Requires the app to support it.
Restart= — when to restart automatically:
no— never (default)on-failure— on non-zero exit code or signalalways— always, even on clean exiton-abnormal— on signals and watchdog timeout, not clean exit
[Install]
[Install]
WantedBy=multi-user.target
WantedBy=multi-user.target is the right setting for most server
services — it means "start this when the system reaches multi-user mode." This is
what systemctl enable uses to create the symlink.
Essential systemctl commands
| Command | What it does |
|---|---|
systemctl start foo | Start now |
systemctl stop foo | Stop now |
systemctl restart foo | Stop then start |
systemctl reload foo | Reload config without full restart (if supported) |
systemctl enable foo | Start on boot |
systemctl disable foo | Don't start on boot |
systemctl status foo | Current state + recent log lines |
systemctl is-active foo | Exits 0 if running (useful in scripts) |
systemctl is-enabled foo | Exits 0 if enabled for boot |
systemctl cat foo | Print the unit file as loaded (including overrides) |
systemctl list-units --type=service | All active service units |
systemctl list-units --failed | Units in failed state |
systemctl daemon-reload | Re-read unit files after changes |
Reading logs with journalctl
# All logs for a service
journalctl -u myservice
# Follow live (like tail -f)
journalctl -fu myservice
# Last 50 lines
journalctl -u myservice -n 50
# Since last boot
journalctl -u myservice -b
# Time range
journalctl -u myservice --since "1 hour ago"
journalctl -u myservice --since "2025-08-01" --until "2025-08-02"
# Without paging (pipe to grep etc.)
journalctl -u myservice --no-pager | grep error
Quick example: a minimal custom service
# /etc/systemd/system/myapp.service
[Unit]
Description=My application
After=network.target
[Service]
Type=simple
User=myapp
ExecStart=/usr/local/bin/myapp
Restart=on-failure
RestartSec=10s
[Install]
WantedBy=multi-user.target
systemctl daemon-reload
systemctl enable --now myapp
--now enables and starts in one step. Useful shortcut.