systemd 全攻略:systemctl / journalctl / .service 文件
这是 Linux 系列的第 17 篇——进程与并发章节收尾。前两篇讲了进程怎么来、怎么收信号——这一篇讲怎么让系统自动管理这些进程。
0. systemd 是什么
Linux 启动后的第一个用户进程(PID 1)一定有一个——历史上叫 init,今天 95% 的发行版用 systemd:
$ ps -p 1 -o pid,comm
PID COMMAND
1 systemd
systemd 接管的事远不止"启动其他服务"——它一口气吞下了:
- 服务管理(替代 init scripts、SysV init)
- 日志收集(journald,替代 syslog 部分功能)
- 定时任务(systemd timer,替代 cron)
- 网络管理(systemd-networkd,替代 ifupdown)
- DNS 解析(systemd-resolved)
- 登录会话(logind,管 tty / SSH session)
- 挂载点管理(automount)
学一个东西管这么多功能——褒贬不一,但现代 Linux 生态已经定型。这一篇先把"服务管理 + 日志 + 定时"这 3 个最常用的拿下,剩下用到时再补。
1. systemctl:服务控制
最高频 5 条命令:
# 状态:服务在跑吗?最近一次怎么了?
$ systemctl status nginx
● nginx.service - A high performance web server
Loaded: loaded (/lib/systemd/system/nginx.service; enabled; preset: enabled)
Active: active (running) since Tue 2026-05-25 10:14:23 CST; 2h 30min ago
Docs: man:nginx(8)
Main PID: 12345 (nginx)
Tasks: 5 (limit: 4708)
Memory: 18.6M
CGroup: /system.slice/nginx.service
├─12345 "nginx: master process"
└─12346 "nginx: worker process"
# 启 / 停 / 重启
$ sudo systemctl start nginx
$ sudo systemctl stop nginx
$ sudo systemctl restart nginx
$ sudo systemctl reload nginx # 不重启,发 SIGHUP 重载配置
# 开机自启 / 取消
$ sudo systemctl enable nginx
$ sudo systemctl disable nginx
$ sudo systemctl enable --now nginx # 一次性:启 + 设自启
列出系统所有服务
# 当前活跃的
$ systemctl list-units --type=service
# 所有定义过的(含未启动 / 已停)
$ systemctl list-unit-files --type=service
# 只看挂了的
$ systemctl --failed
--failed 是开机后第一件事——看哪些服务没起来。
2. 写一个 .service 文件:让你的进程开机自启
假设你写了一个 Python 脚本 ~/myapp.py,想让它做成服务跑:
$ sudo vim /etc/systemd/system/myapp.service
最小可用模板:
[Unit]
Description=My App
After=network.target
[Service]
Type=simple
User=tenggouwa
WorkingDirectory=/home/tenggouwa
ExecStart=/usr/bin/python3 /home/tenggouwa/myapp.py
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
逐字段解释:
[Unit]:元信息 + 依赖
| 字段 | 含义 |
|---|---|
Description= |
给人看的描述,systemctl status 显示 |
After= |
"我要在这些之后启动"(network.target 是网络就绪) |
Requires= |
"这些必须先成功,我才能起"(更强) |
Wants= |
"希望这些先起,但它们失败我也可以起"(最弱) |
[Service]:进程本身
| 字段 | 含义 |
|---|---|
Type=simple |
进程前台跑,systemd 直接 fork+exec(最常用) |
Type=forking |
老式 daemon:进程会自己 fork 后退出,要配 PIDFile= |
Type=notify |
进程通过 sd_notify 告诉 systemd "我准备好了" |
Type=oneshot |
跑完就退出(脚本类) |
User=, Group= |
用什么身份跑(不要用 root 跑业务进程) |
WorkingDirectory= |
cwd |
ExecStart= |
主命令(必须绝对路径,systemd 不查 PATH) |
ExecStartPre=/Post= |
起前 / 起后跑的额外命令 |
ExecReload= |
reload 时跑的命令(如 nginx 是 /usr/sbin/nginx -s reload) |
Restart=on-failure |
失败时自动拉起(也可以 always / no) |
RestartSec=5 |
重启间隔 5 秒 |
Environment="K=V" |
设环境变量 |
EnvironmentFile= |
从文件读环境变量(推荐放 secret) |
StandardOutput=/StandardError= |
输出去哪(默认 journal) |
[Install]:什么时候被启用
[Install]
WantedBy=multi-user.target
意思是"系统进入 multi-user.target(普通多用户运行级别)时拉起我"。enable 时实际是建一个符号链接:
$ sudo systemctl enable myapp
Created symlink /etc/systemd/system/multi-user.target.wants/myapp.service
→ /etc/systemd/system/myapp.service.
让它跑起来
$ sudo systemctl daemon-reload # 改了 unit 文件后必跑
$ sudo systemctl start myapp
$ systemctl status myapp # 看是不是真的起了
$ sudo systemctl enable myapp # 开机自启
3. 安全 / 资源限制(强烈推荐加)
业务进程不该裸跑——systemd 提供大量限制选项,几行配置等于一个迷你容器:
[Service]
# ...上面那些...
# 资源限制
MemoryMax=512M # 超了会被 OOM killer
CPUQuota=50% # 最多用半核
TasksMax=100 # 最多 fork 100 个进程
# 安全沙箱(参考 systemd.exec(5))
NoNewPrivileges=yes # 不允许 suid 提权
PrivateTmp=yes # /tmp 隔离(独立 namespace)
ProtectSystem=strict # /usr /boot /etc 全部只读
ProtectHome=yes # /home 不可见
ReadWritePaths=/var/lib/myapp # 唯一可写的路径
PrivateDevices=yes # 不给硬件访问
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 # 只准这几类 socket
SystemCallFilter=@system-service # syscall 白名单
这些选项基本上是"零成本"的 hardening——挂上一两行就让你的服务比裸跑安全 N 倍。
想看 systemd 自带的所有沙箱选项:
man systemd.exec,强烈推荐通读一遍。
4. journalctl:看日志的标准方式
systemd 的服务 stdout/stderr 默认进 journald——一个二进制的日志数据库。要查:
# 看某服务的日志
$ journalctl -u nginx
$ journalctl -u nginx -n 100 # 最后 100 行
$ journalctl -u nginx -f # 实时跟(类似 tail -f)
# 按时间段
$ journalctl -u nginx --since "1 hour ago"
$ journalctl -u nginx --since "2026-05-25" --until "2026-05-26"
$ journalctl -u nginx --since today
$ journalctl -u nginx --since "10 min ago"
# 看本次开机以来的(重启后)
$ journalctl -b
$ journalctl -b -1 # 上次开机的
$ journalctl -b -2 # 再上次
# 按优先级筛(0=emerg ... 7=debug)
$ journalctl -u nginx -p err # err 及以上
# 按 PID
$ journalctl _PID=12345
# 实时跟踪某个匹配
$ journalctl -f -u nginx -p warning
内核日志
$ journalctl -k # = dmesg
$ journalctl -k -f # 实时跟内核消息
OOM kill、磁盘错误、网卡 link 状态——都在这里。
谁占了 journal 磁盘
$ journalctl --disk-usage
Archived and active journals take up 1.2G in the file system.
# 限制大小
$ sudo journalctl --vacuum-size=500M
$ sudo journalctl --vacuum-time=7d # 只保留 7 天
或者改配置 /etc/systemd/journald.conf:
SystemMaxUse=500M
MaxRetentionSec=1week
5. systemd timer:cron 的现代替代
要每天凌晨 3 点跑备份脚本——cron 写:
0 3 * * * /usr/local/bin/backup.sh
systemd timer 写两个文件:
/etc/systemd/system/backup.service:
[Unit]
Description=Daily backup
[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh
/etc/systemd/system/backup.timer:
[Unit]
Description=Daily backup timer
[Timer]
OnCalendar=*-*-* 03:00:00 # 每天 3:00
Persistent=true # 错过了开机后补跑
RandomizedDelaySec=10min # 加 10 分钟内随机延迟(避免雷击)
[Install]
WantedBy=timers.target
启用:
$ sudo systemctl daemon-reload
$ sudo systemctl enable --now backup.timer
# 看所有 timer
$ systemctl list-timers
NEXT LEFT LAST PASSED UNIT
Wed 2026-05-26 03:00:09 CST 11h left Tue 2026-05-25 03:00:14 CST 12h ago backup.timer
...
timer 比 cron 强在哪?
- 跑失败有 journald 日志可查(cron 默认发邮件,邮件没配的话默默失败)
- 服务可以被手动 systemctl start 触发(不用等时间到)
Persistent=true让错过的运行能补(关机几小时再开机也会补一次)- 依赖管理(timer 可以
After=其他服务) - 统一资源限制 / 沙箱(跟普通 service 一样能用 MemoryMax / NoNewPrivileges)
唯一缺点:要写两个文件,比 cron 一行麻烦。
OnCalendar= 语法速查
*-*-* 03:00:00 # 每天 3 点
Mon *-*-* 09:00:00 # 每周一 9 点
*-*-* *:00/15:00 # 每 15 分钟
*-*-01 00:00:00 # 每月 1 号 0 点
hourly daily weekly monthly yearly # 简写
测试 OnCalendar:
$ systemd-analyze calendar 'Mon *-*-* 09:00:00'
Normalized form: Mon *-*-* 09:00:00
Next elapse: Mon 2026-06-01 09:00:00 CST
From now: 6 days left
6. user-level service:不要 root
systemd 不止 /etc/systemd/system/,每个用户还有自己的:
$ mkdir -p ~/.config/systemd/user
$ vim ~/.config/systemd/user/myapp.service
用法一样,但前缀 --user:
$ systemctl --user start myapp
$ systemctl --user enable myapp
$ journalctl --user -u myapp
好处:
- 不用 sudo
- 服务跟 session 绑(用户登出可能死,除非
loginctl enable-linger <user>) - 写个人自动化脚本最适合
7. 看 service 的"血缘"和资源占用
# 看树形视图(哪些 service 属于哪个 slice)
$ systemd-cgls
# 看 CPU/Mem 占用(top 风格)
$ systemd-cgtop
# 看某 service 的所有进程
$ systemctl status nginx
# 或者
$ pgrep -af 'nginx'
# 看 unit 文件实际加载的内容(包含 drop-ins)
$ systemctl cat nginx
systemctl cat 特别好用——你装一个 service,加了 /etc/systemd/system/nginx.service.d/override.conf 改了点东西,systemctl cat 会把所有覆盖逻辑展开给你看。
8. 调试一个跑不起来的 service
# 1. 看 status:systemd 通常告诉你大致原因
$ systemctl status myapp
# 2. 看完整日志
$ journalctl -u myapp -n 50 --no-pager
# 3. 实时跟
$ journalctl -u myapp -f
# 4. 排除 sandbox 配置写错(systemd 沙箱失败也算启动失败)
$ systemd-analyze security myapp.service
# 给你的服务打"安全分",并列出每条沙箱选项是否阻止它启动
# 5. 手动跑 ExecStart 命令试试
$ sudo -u myuser /usr/bin/python3 /home/myuser/myapp.py
# 能跑就是 systemd 环境的问题(PATH / 工作目录 / 环境变量)
systemd 启动失败时最常见的 4 个坑:
- ExecStart 不是绝对路径(写了
python3 app.py→ 找不到) - WorkingDirectory 没设,应用读相对路径配置文件失败
- PATH / 环境变量没传进来(systemd 默认 PATH 很短)
- 沙箱限制太严(如
ProtectSystem=strict但应用要写 /usr/share)
9. 一个完整可抄的生产级 unit
把上面所有最佳实践糅在一起——一个能直接抄的 web app 模板:
[Unit]
Description=My Awesome App
After=network-online.target postgresql.service
Wants=network-online.target
[Service]
Type=simple
User=myapp
Group=myapp
WorkingDirectory=/opt/myapp
EnvironmentFile=/etc/myapp/env
ExecStart=/opt/myapp/.venv/bin/python /opt/myapp/server.py
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=5s
TimeoutStopSec=30s
# 资源
MemoryMax=512M
CPUQuota=80%
TasksMax=200
# 沙箱
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict
ProtectHome=yes
ReadWritePaths=/var/lib/myapp /var/log/myapp
PrivateDevices=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
LockPersonality=yes
RestrictRealtime=yes
SystemCallFilter=@system-service
SystemCallErrorNumber=EPERM
# 日志
StandardOutput=journal
StandardError=journal
SyslogIdentifier=myapp
[Install]
WantedBy=multi-user.target
读一遍每一行——这就是"生产级"的标准。
10. 现在做一件事
# 1. 看你机器上哪些 service 在跑
$ systemctl list-units --type=service --state=running | head -15
# 2. 有失败的吗
$ systemctl --failed
# 3. 今天系统启动用了多久
$ systemd-analyze
Startup finished in 1.234s (kernel) + 6.789s (userspace) = 8.023s
# 4. 哪些 service 启动最慢
$ systemd-analyze blame | head -10
# 5. 看你 systemd 版本
$ systemctl --version | head -1
把"开机自启 / 看日志 / 定时跑"这三个最常见的活拿下,systemd 这一坨就够你用 90% 的场景了。
下一篇:net-tools——
ip / ss / dig / curl / nc这套现代 Linux 网络工具集,看连接、查路由、查 DNS、测连通、找谁占了端口。