Chapter 13

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

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":

[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.FileListener to 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: โ‘  change After=network.target to After=network-online.target for precise network readiness, โ‘ก use WantedBy instead of Requires for 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.service to get a sandbox security score (0-10, higher is safer). The config above with NoNewPrivileges/PrivateTmp/ProtectSystem/ProtectHome should achieve around 7+.

  Previous
  โ† Ch12: Engineering


  Next
  Ch14: Performance โ†’
Rate this chapter
4.7  / 5  (21 ratings)

๐Ÿ’ฌ Comments