systemd
Chapter 13: systemd Deep Dive
systemd is the init system and service manager of modern Linux distributions, running as PID 1 to bootstrap userspace, manage service lifecycles, collect logs, and control cgroup resources. This chapter moves from architecture principles through unit file authoring, service management, timers, log queries, and performance analysis โ culminating in a complete production-grade systemd config for a Go web service.
1. systemd Architecture and Unit Types
systemd runs as PID 1 โ the very first userspace process after the kernel boots. It starts services in parallel once their dependencies are satisfied, dramatically reducing boot time. systemd uses units as its management objects, each unit type representing a category of resource or behavior:
| Unit Type | Extension | Purpose |
|---|---|---|
| service | .service | Daemon or one-shot command |
| socket | .socket | Network/Unix socket, activate service on demand |
| timer | .timer | Scheduled trigger, replaces cron |
| path | .path | Triggered by filesystem path changes |
| mount | .mount | Mount point management |
| target | .target | Service grouping sync-point (similar to runlevel) |
| slice | .slice | cgroup hierarchy resource partition |
| device | .device | Kernel device exposure |
Dependency Keywords
- Requires= โ Hard dependency: if dependency fails, this unit fails too
- Wants= โ Soft dependency: failure does not stop this unit
- After= โ Ordering: start after the specified unit
- Before= โ Ordering: start before the specified unit
- BindsTo= โ Strong binding: if dependency stops, this unit stops
- Conflicts= โ Mutual exclusion: starting this unit stops the conflicting one
Target Concept
Targets are synchronization points for units, analogous to SysV runlevels. Common targets: multi-user.target (multi-user without GUI, equivalent to runlevel 3) and graphical.target (desktop environment, runlevel 5). default.target is the system's boot target, usually symlinked to graphical.target.
2. systemctl Command Reference
Service Lifecycle
# ๅฏๅจ / ๅๆญข / ้ๅฏ / ไผ้
้่ฝฝ้
็ฝฎ๏ผไธ้ๅฏ่ฟ็จ๏ผ
systemctl start nginx.service
systemctl stop nginx.service
systemctl restart nginx.service
systemctl reload nginx.service # ๅ้ SIGHUP๏ผ่ฆๆฑๆๅกๆฏๆ
# ๅผๆบ่ชๅฏ / ็ฆ็จ / ๅฑ่ฝ๏ผmask ้ฒๆญขไปปไฝๆนๅผๅฏๅจ๏ผ
systemctl enable nginx.service
systemctl disable nginx.service
systemctl mask nginx.service
systemctl unmask nginx.service
# ๆฅ็็ถๆ๏ผๅซๆ่ฟๆฅๅฟ๏ผ
systemctl status nginx.service
# ้ๆฐ่ฏปๅ็ฃ็ไธ็ unit ๆไปถ๏ผๆนๅฎ้
็ฝฎๅฟ
้กปๆง่ก๏ผ
systemctl daemon-reload
State Queries
# ้ๅบ็ 0=active / ้0=inactive
systemctl is-active nginx.service
systemctl is-enabled nginx.service
systemctl is-failed nginx.service
# ๅๅบๆๆๅทฒๅ ่ฝฝ็ unit
systemctl list-units
systemctl list-units --type=service --state=running
# ๅๅบๆๆ unit ๆไปถๅๅ
ถ enable ็ถๆ
systemctl list-unit-files --type=service
# ๆฅ็ๆ unit ็ๆๆไพ่ตๆ
systemctl list-dependencies nginx.service
Tip: In scripts, use
systemctl is-active --quiet nginxโ quiet mode returns only an exit code without output, making it easy to use in conditionals.
3. Writing .service Unit Files
System-level unit files live in /etc/systemd/system/ (highest priority) or /lib/systemd/system/ (distribution-provided). Custom services always belong in /etc/systemd/system/.
[Unit] Section: Description and Dependencies
[Unit]
Description=My Application Service
Documentation=https://example.com/docs
After=network-online.target postgresql.service
Wants=network-online.target
Requires=postgresql.service
[Service] Section: Process Control
Type= controls how systemd determines the service is "ready":
- simple โ default; ExecStart process is the main process
- forking โ service forks and parent exits (traditional daemon)
- oneshot โ runs a one-time task and exits
- notify โ service calls sd_notify() to signal readiness
- exec โ like simple, but waits until exec() completes
[Service]
Type=simple
User=appuser
Group=appgroup
WorkingDirectory=/opt/myapp
# ็ฏๅขๅ้๏ผ็ดๆฅๅๆไปๆไปถ่ฏป๏ผ
Environment=APP_ENV=production
Environment=PORT=8080
EnvironmentFile=/etc/myapp/env # ๆไปถไธญๆฏ่ก KEY=VALUE
ExecStart=/opt/myapp/bin/server
ExecStop=/bin/kill -s TERM $MAINPID
ExecReload=/bin/kill -s HUP $MAINPID
# ่ชๅจ้ๅฏ็ญ็ฅ
Restart=on-failure
RestartSec=5s
StartLimitIntervalSec=60s
StartLimitBurst=3
# ่ตๆบ้ๅถ
LimitNOFILE=65536
LimitNPROC=4096
[Install] Section: Installation Target
[Install]
WantedBy=multi-user.target
Note: After modifying a unit file you must run
systemctl daemon-reload, otherwise systemd continues using the in-memory old config even if the file changed.
4. .timer Units: Replacing cron
A systemd timer requires a paired .service unit with the same base name. When the timer fires, systemd starts the corresponding service.
# /etc/systemd/system/backup.timer
[Unit]
Description=Daily database backup timer
[Timer]
# ็ณป็ปๅฏๅจๅ 5 ๅ้ๆง่กไธๆฌก
OnBootSec=5min
# ๆฏๆฌก่งฆๅๅ 24 ๅฐๆถๅๆฌก่งฆๅ
OnUnitActiveSec=24h
# ๆ่
ไฝฟ็จ cron ้ฃๆ ผ๏ผๆฏๅคฉ 03:00๏ผ
OnCalendar=*-*-* 03:00:00
# ็ณป็ปๅ
ณๆบๆ้ด้่ฟ็ไปปๅก๏ผๅผๆบๅ่กฅๆง่ก
Persistent=true
# ้ๆบๅปถ่ฟ๏ผ้ฟๅ
ๆด็นๆต้ๆดชๅณฐ
RandomizedDelaySec=300
[Install]
WantedBy=timers.target
# /etc/systemd/system/backup.service
[Unit]
Description=Database backup job
[Service]
Type=oneshot
User=backup
ExecStart=/usr/local/bin/backup.sh
# ๅฏ็จๅนถๅฏๅจ timer๏ผๆณจๆๅฏ็จ็ๆฏ timer ่ไธๆฏ service๏ผ
systemctl enable --now backup.timer
# ๆฅ็ๆๆ timer ๅไธๆฌก่งฆๅๆถ้ด
systemctl list-timers --all
crontab vs systemd timer Comparison
| Feature | crontab | systemd timer |
|---|---|---|
| Configuration | cron expression | OnCalendar / OnBootSec / OnUnitActiveSec |
| Missed job catch-up | Not supported | Supported via Persistent=true |
| Log integration | Manual setup required | Automatic via journald |
| Resource limits | Not supported | Via .service cgroup settings |
| Dependency management | Not supported | Full unit dependency graph |
| Randomized delay | Manual sleep required | RandomizedDelaySec |
5. .socket Units: Socket Activation
Socket activation is one of systemd's core features: systemd pre-creates and holds the socket, only starting the corresponding service when a client connects, achieving zero listen latency and faster boot.
# /etc/systemd/system/myapp.socket
[Unit]
Description=MyApp TCP socket
[Socket]
ListenStream=0.0.0.0:8080
# Unix domain socket ็คบไพ
# ListenStream=/run/myapp.sock
# ๆฐๆฎๆฅ socket๏ผUDP๏ผ
# ListenDatagram=514
# socket ไผ ็ปๆๅกๅไฟๆ็ๅฌ๏ผๅคๅฎไพๅบๆฏ๏ผ
Accept=false
[Install]
WantedBy=sockets.target
How it works: The service receives the socket via file descriptor inheritance (starting at SD_LISTEN_FDS_START=3) without calling bind()/listen() itself. In Go, use
net.FileListenerto create a Listener from the inherited fd.
6. .path Units: Filesystem-Triggered Activation
# /etc/systemd/system/process-uploads.path
[Unit]
Description=Watch upload directory for new files
[Path]
# ่ทฏๅพๅญๅจๆถ่งฆๅ๏ผ้ฆๆฌกๆฃๆต๏ผ
PathExists=/var/spool/uploads/trigger
# ่ทฏๅพๅ
ๅฎนๅๅๆถ่งฆๅ๏ผinotify IN_CLOSE_WRITE๏ผ
PathChanged=/var/spool/uploads
# ่ทฏๅพ่ขซไฟฎๆนๆถ่งฆๅ๏ผๅ
ๆฌๅฑๆงๅๅ๏ผ
PathModified=/var/spool/uploads
# ่งฆๅๅๅฏๅจๅๅ .service๏ผๆ็จ Unit= ๆๅฎๅ
ถไปๆๅก๏ผ
Unit=process-uploads.service
[Install]
WantedBy=multi-user.target
7. journalctl Complete Guide
journald is systemd's built-in structured logging system, replacing traditional syslog. All service stdout/stderr goes into the journal automatically โ no log file path configuration required.
# ๆฅ็ๆไธชๆๅก็ๆฅๅฟ๏ผ-u ่ฟๆปค unit๏ผ
journalctl -u nginx.service
# ๅฎๆถ่ท่ธช๏ผ็ฑปไผผ tail -f๏ผ
journalctl -u nginx.service -f
# ๆถ้ด่ๅด่ฟๆปค
journalctl -u nginx --since "2025-01-01 00:00:00" --until "2025-01-02 00:00:00"
journalctl -u nginx --since "1 hour ago"
journalctl -u nginx --since yesterday
# ๆไผๅ
็บง่ฟๆปค๏ผemerg/alert/crit/err/warning/notice/info/debug๏ผ
journalctl -p err # ๅช็ err ๅๆด้ซ็บงๅซ
journalctl -p warning..err # ่ๅด่ฟๆปค
# ๅฝๅๅฏๅจ็ๆฅๅฟ
journalctl -b
journalctl -b -1 # ไธๆฌกๅฏๅจ
journalctl --list-boots # ๅๅบๆๆๅฏๅจ่ฎฐๅฝ
# ่พๅบๆ ผๅผ
journalctl -u nginx -o json # JSON ๆ ผๅผ๏ผๆฏๆกไธ่ก
journalctl -u nginx -o json-pretty # ๆ ผๅผๅ JSON
journalctl -u nginx -o verbose # ๆๆๅญๆฎต
journalctl -u nginx -o short-iso # ISO ๆถ้ดๆณ
# ๆฅๅฟ็ฃ็ๅ ็จไธๆธ
็
journalctl --disk-usage
journalctl --vacuum-size=500M # ๆธ
็ๅฐ 500MB
journalctl --vacuum-time=30d # ๆธ
็ 30 ๅคฉไปฅๅ็ๆฅๅฟ
# ๆ่ฟ็จ/็จๆท่ฟๆปค
journalctl _PID=1234
journalctl _UID=1000
# ๅ
ๆ ธๆฅๅฟ
journalctl -k # ็ญๅ dmesg
Production tip: Persist journal logs: by default they live in
/run/log/journal/(lost on reboot). Create/var/log/journal/and restart journald to persist logs to/var/log/journal/.
8. cgroup v2 Resource Control
systemd creates an individual cgroup for each service. Resource limits can be set directly in the [Service] section of the unit file without manually touching /sys/fs/cgroup.
[Service]
# CPU ้
้ข๏ผๆๅคไฝฟ็จ 2 ไธชๆ ธๅฟ็ๆถ้ด๏ผ200% ่กจ็คบ 2 ๆ ธ๏ผ
CPUQuota=200%
# CPU ๆ้๏ผ็ธๅฏน่ฐๅบฆไผๅ
็บง๏ผ้ป่ฎค 100๏ผ่ๅด 1-10000๏ผ
CPUWeight=200
# ๅ
ๅญ้ๅถ
MemoryMax=512M # ็กฌไธ้๏ผ่ถ
่ฟ่งฆๅ OOM kill
MemoryHigh=400M # ่ฝฏไธ้๏ผ่ถ
่ฟๅผๅง่ๆต
MemorySwapMax=0 # ็ฆๆญขไฝฟ็จ swap
# IO ๆ้๏ผ็ธๅฏน๏ผ้ป่ฎค 100๏ผ
IOWeight=50
# ไปปๅก๏ผ็บฟ็จ๏ผๆฐ้ๅถ
TasksMax=128
# ๅฎๆถ็ๆงๅ cgroup ่ตๆบๅ ็จ๏ผ็ฑปไผผ top๏ผ
systemd-cgtop
# ๆฅ็ๆๆๅก็ cgroup ่ทฏๅพ
systemctl status nginx | grep "CGroup"
# ็ดๆฅๆฅ็ cgroup v2 ๅฑ็บงๆ
ls /sys/fs/cgroup/system.slice/nginx.service/
# ๆฅ็ๆ cgroup ็ CPU ็ป่ฎก
cat /sys/fs/cgroup/system.slice/nginx.service/cpu.stat
9. systemd-analyze: Boot Performance
# ๆปไฝๅฏๅจๆถ้ดๅ่งฃ
systemd-analyze
# ๅ unit ๅฏๅจ่ๆถๆ่ก๏ผๆๆ
ข็ๅจๆไธ้ข๏ผ
systemd-analyze blame
# ๅ
ณ้ฎ่ทฏๅพ๏ผๅณๅฎๆ็ปๅฏๅจๆถ้ด็ไพ่ต้พ๏ผ
systemd-analyze critical-chain
# ็ๆๅฏๅจๆถๅบ SVG ๅพ๏ผๅฏๅจๆต่งๅจๆๅผ๏ผ
systemd-analyze plot > boot.svg
# ๆฃๆฅ unit ๆไปถ่ฏญๆณ
systemd-analyze verify /etc/systemd/system/myapp.service
# ๅๆ็นๅฎ unit ็ๅฎๅ
จ่ฏๅ
systemd-analyze security nginx.service
Optimization tip: After finding bottlenecks with
systemd-analyze blame, check whether you can: โ changeAfter=network.targettoAfter=network-online.targetfor precise network readiness, โก useWantedByinstead ofRequiresfor non-critical services, โข use socket activation to defer service startup.
10. User-level systemd
Each logged-in user can run their own systemd instance to manage user-level services without root. Unit files go in ~/.config/systemd/user/.
# ็จๆท็บง systemctl๏ผๅ --user ๆ ๅฟ๏ผ
systemctl --user start myservice
systemctl --user enable myservice
systemctl --user status myservice
# ๆฅ็็จๆท็บง journal
journalctl --user -u myservice -f
# ๅ
่ฎธๆๅกๅจ็จๆทๆณจ้ๅ็ปง็ปญ่ฟ่ก๏ผ้ป่ฎคๆณจ้ๆถๅๆญข๏ผ
loginctl enable-linger $USER
# ็จๆท็บง unit ๆไปถ่ทฏๅพ
mkdir -p ~/.config/systemd/user/
# ๆพ็ฝฎ unit ๅ้่ฆ reload
systemctl --user daemon-reload
11. Practice: Complete systemd Config for a Go Web Service
The following is a production-ready systemd configuration for a Go web service, including security hardening, automatic restart, resource limits, and log configuration.
# /etc/systemd/system/goapp.service
[Unit]
Description=Go Web Application Server
Documentation=https://github.com/myorg/goapp
After=network-online.target postgresql.service redis.service
Wants=network-online.target
Requires=postgresql.service
[Service]
Type=notify
User=goapp
Group=goapp
WorkingDirectory=/opt/goapp
# ไบ่ฟๅถๅ้
็ฝฎ
ExecStart=/opt/goapp/bin/goapp serve --config /etc/goapp/config.yaml
ExecReload=/bin/kill -s HUP $MAINPID
# ็ฏๅขๅ้
Environment=GOMAXPROCS=4
EnvironmentFile=-/etc/goapp/env # - ๅ็ผ่กจ็คบๆไปถไธๅญๅจๆถไธๆฅ้
# ่ชๅจ้ๅฏ๏ผ้0้ๅบ็ ๆไฟกๅท้ๅบๆถ้ๅฏ๏ผๆๅค5ๅ้ๅ
3ๆฌก
Restart=on-failure
RestartSec=5s
StartLimitIntervalSec=300s
StartLimitBurst=3
# ๅฎๅ
จๅ ๅบ
NoNewPrivileges=yes
PrivateTmp=yes # ็ฌ็ซ /tmp ็ฎๅฝ
ProtectSystem=strict # ๅช่ฏปๆ่ฝฝ /usr /boot /etc
ProtectHome=yes # ๆ ๆณ่ฏปๅๅฎถ็ฎๅฝ
ReadWritePaths=/var/lib/goapp /var/log/goapp
CapabilityBoundingSet=CAP_NET_BIND_SERVICE # ๅชๅ
่ฎธ็ปๅฎไฝ็ซฏๅฃ
AmbientCapabilities=CAP_NET_BIND_SERVICE
# ่ตๆบ้ๅถ
LimitNOFILE=65536
CPUQuota=400%
MemoryMax=1G
MemoryHigh=800M
TasksMax=256
# ๆ ๅ่พๅบ็ดๆฅ่ฟ journal
StandardOutput=journal
StandardError=journal
SyslogIdentifier=goapp
[Install]
WantedBy=multi-user.target
Companion timer: scheduled log cleanup
# /etc/systemd/system/goapp-cleanup.timer
[Unit]
Description=GoApp log cleanup timer
[Timer]
OnCalendar=Sun *-*-* 02:00:00
Persistent=true
RandomizedDelaySec=600
[Install]
WantedBy=timers.target
---
# /etc/systemd/system/goapp-cleanup.service
[Unit]
Description=GoApp log cleanup
[Service]
Type=oneshot
User=goapp
ExecStart=/opt/goapp/scripts/cleanup-logs.sh
# ้จ็ฝฒๆญฅ้ชค
sudo cp goapp.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now goapp.service
# ้ช่ฏ็ถๆ
sudo systemctl status goapp.service
sudo journalctl -u goapp.service -f
# ๆฅ็ๆๅก็ cgroup ่ตๆบๅ ็จ
sudo systemd-cgtop -d 1
Security score: Run
systemd-analyze security goapp.serviceto get a sandbox security score (0-10, higher is safer). The config above withNoNewPrivileges/PrivateTmp/ProtectSystem/ProtectHomeshould achieve around 7+.
Previous
โ Ch12: Engineering
Next
Ch14: Performance โ