本文整理 systemd servicesystemd timer 以及常见相关 unit 的使用方法。

重点目标不是背配置项,而是建立一条清晰的阅读路径:

1
2
3
4
5
6
7
systemd 管什么
-> unit 有哪些类型
-> service 怎么描述一个服务
-> target / enable 怎么决定开机自启
-> timer 怎么替代 cron
-> socket / path / mount 这类 unit 什么时候用
-> 出问题怎么排查

1. systemd 和 unit 是什么

systemd 是 Linux 上常见的系统与服务管理器。

它负责管理:

1
2
3
4
5
6
7
8
系统启动流程
后台服务
定时任务
挂载点
socket 监听
路径变化触发
日志
依赖关系

在 systemd 里,被管理的对象统一叫 unit

常见 unit 类型:

1
2
3
4
5
6
.service    后台服务或一次性任务
.timer 定时触发任务
.target 启动阶段或一组 unit 的集合
.socket socket 激活
.mount 挂载点
.path 文件或目录变化触发

一句话:

1
systemd 用 unit 描述系统里的各种资源和任务,再用依赖关系把它们组织起来。

2. 常见 unit 类型速览

unit 类型 作用 典型场景
.service 描述一个服务或任务怎么启动、停止、重启 Java 服务、Nginx、脚本任务
.timer 按时间触发另一个 unit 每天备份、定期清理、健康检查
.target 表示启动阶段或 unit 分组 multi-user.targetnetwork-online.target
.socket 先监听端口,有连接时再拉起服务 按需启动、减少常驻进程
.mount 管理文件系统挂载 挂载数据盘、NFS、临时目录
.path 监听文件或目录变化,变化时触发服务 文件落地后处理、配置变更触发

最常用的是:

1
2
3
.service
.timer
.target

其他类型理解即可,遇到特定场景再深入。

3. unit 文件放在哪里

常见位置:

1
2
3
/etc/systemd/system/        管理员自定义 unit,优先级高
/lib/systemd/system/ Debian/Ubuntu 系统包安装的 unit
/usr/lib/systemd/system/ RHEL/CentOS/部分发行版系统包安装的 unit

实际项目里,自己写的 service 和 timer 一般放:

1
/etc/systemd/system/

例如:

1
2
3
/etc/systemd/system/app-manager.service
/etc/systemd/system/backup-daily.service
/etc/systemd/system/backup-daily.timer

修改或新增 unit 文件后,需要让 systemd 重新加载配置:

1
sudo systemctl daemon-reload

4. Target 是什么

target 可以理解成 systemd 的“启动阶段”或“unit 分组”。

它本身通常不启动进程,而是把一组 unit 组织起来。

常见 target:

target 含义
basic.target 基础系统能力就绪
network.target 网络管理服务已启动,但不一定真正联网
network-online.target 网络已经尽量达到可用状态
multi-user.target 多用户命令行环境,服务器常用
graphical.target 图形界面环境
timers.target 定时器集合
sockets.target socket unit 集合

服务器上最常见的是:

1
2
[Install]
WantedBy=multi-user.target

意思是:

1
当系统进入 multi-user.target 这个阶段时,希望这个服务也被拉起来。

这就是很多后台服务开机自启会挂到 multi-user.target 的原因。

5. start 和 enable 的区别

这是理解 systemd 很关键的一点。

1
sudo systemctl start app.service

意思是:

1
现在立刻启动这个服务。

但它不会保证下次开机自动启动。

1
sudo systemctl enable app.service

意思是:

1
把这个服务挂到某个 target 上,让它以后随系统启动。

enable 具体挂到哪里,取决于 unit 文件里的 [Install]

例如:

1
2
[Install]
WantedBy=multi-user.target

执行:

1
sudo systemctl enable app.service

systemd 大致会创建类似这样的符号链接:

1
2
/etc/systemd/system/multi-user.target.wants/app.service
-> /etc/systemd/system/app.service

所以开机进入 multi-user.target 时,systemd 会发现:

1
multi-user.target 想要 app.service

于是就会启动它。

6. 为什么没有 [Install] 就不能直接 enable

[Install] 定义的是:

1
这个 unit 被 enable 时,应该挂到哪个 target 或哪个 unit 的依赖目录里。

如果没有 [Install],systemd 就不知道:

1
2
3
开机时应该让谁拉起它
它应该挂到哪个 target
应该生成什么 wants/requires 符号链接

所以通常不能直接:

1
sudo systemctl enable xxx.service

没有 [Install] 的 unit 仍然可以:

1
sudo systemctl start xxx.service

也可以被别的 unit 通过依赖关系触发。

典型例子:

1
2
backup.service 没有 [Install]
backup.timer 有 [Install]

这表示:

1
2
3
不要开机直接启动 backup.service
而是开机启用 backup.timer
由 timer 到时间后触发 backup.service

7. systemctl 常用命令

查看状态:

1
2
3
systemctl status app.service
systemctl is-active app.service
systemctl is-enabled app.service

启动、停止、重启:

1
2
3
4
sudo systemctl start app.service
sudo systemctl stop app.service
sudo systemctl restart app.service
sudo systemctl reload app.service

开机自启:

1
2
3
sudo systemctl enable app.service
sudo systemctl disable app.service
sudo systemctl enable --now app.service

查看日志:

1
2
3
journalctl -u app.service
journalctl -u app.service -n 100 --no-pager
journalctl -u app.service -f

重新加载 unit 文件:

1
sudo systemctl daemon-reload

查看失败服务:

1
systemctl --failed

8. Service 是什么

.service 用来描述一个服务或任务。

它可以管理:

1
2
3
4
长期运行的后台进程
一次性脚本任务
需要失败重启的守护进程
由 timer/socket/path 触发的任务

典型文件:

1
/etc/systemd/system/my-app.service

一个最小示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[Unit]
Description=My App Service
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
WorkingDirectory=/opt/my-app
ExecStart=/usr/bin/java -jar app.jar
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

9. Service 文件结构

[Unit]

描述 unit 的元信息和依赖关系。

常见字段:

1
2
3
4
[Unit]
Description=My App Service
After=network-online.target
Wants=network-online.target

常用含义:

字段 含义
Description 服务说明
After 启动顺序,在某个 unit 之后启动
Before 启动顺序,在某个 unit 之前启动
Wants 弱依赖,希望一起启动
Requires 强依赖,依赖失败会影响当前 unit

注意:

1
2
After 只控制顺序,不代表会自动拉起依赖。
Wants/Requires 才表示依赖关系。

所以常见组合是:

1
2
After=network-online.target
Wants=network-online.target

[Service]

描述服务如何运行。

常见字段:

1
2
3
4
5
6
7
8
[Service]
Type=simple
User=app
Group=app
WorkingDirectory=/opt/my-app
ExecStart=/usr/bin/java -jar app.jar
Restart=on-failure
RestartSec=5

常用含义:

字段 含义
Type 服务启动类型
User 以哪个用户运行
Group 以哪个用户组运行
WorkingDirectory 工作目录
ExecStart 启动命令
ExecReload 重载命令
ExecStop 停止命令
Restart 重启策略
RestartSec 重启间隔
Environment 环境变量
EnvironmentFile 环境变量文件

[Install]

定义 enable 时如何挂到启动目标上。

常见写法:

1
2
[Install]
WantedBy=multi-user.target

含义:

1
执行 systemctl enable 时,把当前 service 挂到 multi-user.target 的 wants 目录里。

如果没有 [Install]

1
2
3
通常不能直接 systemctl enable
但仍然可以手动 start
也可以被 timer/socket/path 等 unit 触发

10. Service Type

Type 决定 systemd 如何判断服务已经启动。

常见类型:

Type 场景 说明
simple 最常用,前台进程 ExecStart 启动的进程就是主进程
forking 老式后台 daemon 进程会 fork 到后台
oneshot 一次性任务 命令执行完成即结束
notify 支持 sd_notify 的服务 服务主动通知 systemd 已就绪
idle 延迟到其他任务后执行 较少用

Java、Node、Go、Python 后台服务大多用:

1
Type=simple

一次性脚本用:

1
Type=oneshot

11. Service 模板

后台服务模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[Unit]
Description=Example Backend Service
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=app
Group=app
WorkingDirectory=/opt/example
ExecStart=/usr/bin/java -jar /opt/example/app.jar
Restart=on-failure
RestartSec=5
SuccessExitStatus=143

[Install]
WantedBy=multi-user.target

适合:

1
2
3
4
Java 后端服务
Node 服务
Go 服务
Python 常驻服务

一次性任务模板

1
2
3
4
5
6
7
8
9
[Unit]
Description=Run Example Job

[Service]
Type=oneshot
User=app
Group=app
WorkingDirectory=/opt/example
ExecStart=/opt/example/run-job.sh

适合:

1
2
3
4
备份脚本
清理脚本
健康检查脚本
由 timer 触发的任务

注意:

1
2
被 timer 触发的 oneshot service 通常不需要 [Install]。
需要 enable 的是 .timer,不是 .service。

12. ExecStart 注意事项

ExecStart 不是普通 shell。

不能直接写复杂 shell 语法:

1
ExecStart=cd /opt/app && java -jar app.jar

推荐写法:

1
2
WorkingDirectory=/opt/app
ExecStart=/usr/bin/java -jar app.jar

如果确实需要 shell:

1
ExecStart=/bin/bash -lc 'cd /opt/app && java -jar app.jar'

建议:

1
2
3
简单命令直接写绝对路径
复杂逻辑放到脚本里
脚本内部自己 set -euo pipefail

示例:

1
ExecStart=/usr/local/sbin/example-watchdog.sh

13. 环境变量

直接写在 unit 里:

1
2
3
[Service]
Environment="JAVA_OPTS=-Xms512m -Xmx512m"
Environment="SPRING_PROFILES_ACTIVE=prod"

使用环境变量文件:

1
2
3
[Service]
EnvironmentFile=/etc/example/app.env
ExecStart=/usr/bin/java $JAVA_OPTS -jar app.jar

/etc/example/app.env

1
2
JAVA_OPTS=-Xms512m -Xmx512m
SPRING_PROFILES_ACTIVE=prod

注意:

1
2
环境变量文件不要放密码明文,除非权限控制非常明确。
敏感信息优先使用专门的密钥管理方案。

14. Timer 是什么

.timer 是 systemd 的定时任务 unit。

它一般不直接执行命令,而是触发同名 .service

例如:

1
2
backup.timer
-> 触发 backup.service

所以通常会有两个文件:

1
2
/etc/systemd/system/backup.service
/etc/systemd/system/backup.timer

一句话:

1
service 描述做什么,timer 描述什么时候做。

15. Timer 文件结构

示例:

1
2
3
4
5
6
7
8
9
10
[Unit]
Description=Run backup daily

[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
Unit=backup.service

[Install]
WantedBy=timers.target

常用字段:

字段 含义
OnCalendar 按日历时间触发
OnBootSec 开机后多久触发
OnUnitActiveSec 上次触发后多久再次触发
OnUnitInactiveSec 上次任务结束后多久再次触发
Persistent 错过时间后补跑
RandomizedDelaySec 增加随机延迟
Unit 触发哪个 unit

如果 timer 和 service 同名,可以省略:

1
Unit=backup.service

16. Timer 模板

每天定时任务

/etc/systemd/system/backup.service

1
2
3
4
5
6
7
[Unit]
Description=Backup data

[Service]
Type=oneshot
User=root
ExecStart=/usr/local/sbin/backup-data.sh

/etc/systemd/system/backup.timer

1
2
3
4
5
6
7
8
9
10
[Unit]
Description=Run backup daily

[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
RandomizedDelaySec=300

[Install]
WantedBy=timers.target

启用:

1
2
sudo systemctl daemon-reload
sudo systemctl enable --now backup.timer

间隔任务

例如每 5 分钟执行一次:

1
2
3
4
5
6
7
8
9
10
[Unit]
Description=Run watchdog every 5 minutes

[Timer]
OnBootSec=1min
OnUnitActiveSec=5min
Unit=watchdog.service

[Install]
WantedBy=timers.target

含义:

1
2
开机 1 分钟后先执行一次
之后每次执行完成并进入 active 计时后,隔 5 分钟再触发

17. 常用 OnCalendar 写法

每天凌晨 3 点:

1
OnCalendar=*-*-* 03:00:00

每小时:

1
OnCalendar=hourly

每天:

1
OnCalendar=daily

每周:

1
OnCalendar=weekly

每月:

1
OnCalendar=monthly

周一到周五 9 点:

1
OnCalendar=Mon..Fri 09:00:00

查看解析结果:

1
systemd-analyze calendar 'Mon..Fri 09:00:00'

18. Timer 常用命令

查看所有 timer:

1
systemctl list-timers --all

查看某个 timer:

1
systemctl status backup.timer

启动 timer:

1
sudo systemctl enable --now backup.timer

手动执行对应 service:

1
sudo systemctl start backup.service

查看日志:

1
2
journalctl -u backup.service
journalctl -u backup.timer

19. Timer 和 Cron 对比

对比项 systemd timer cron
日志 统一进入 journal 依赖 cron/mail/重定向
依赖 支持 unit 依赖 较弱
错过补跑 Persistent=true 默认不补
随机延迟 支持 RandomizedDelaySec 需要自己写
管理 systemctl 统一管理 crontab 管理
适合 系统任务、服务相关任务 简单周期任务

建议:

1
2
服务相关、需要日志和依赖管理的任务,用 systemd timer。
很简单的个人周期命令,用 cron 也可以。

20. Socket Unit

.socket 用来让 systemd 先监听 socket,有连接进来时再启动对应 .service

典型用途:

1
2
3
按需启动服务
减少常驻进程
让 systemd 接管端口监听

示例:

1
2
3
4
5
6
7
8
9
[Unit]
Description=Example socket

[Socket]
ListenStream=8080
Accept=no

[Install]
WantedBy=sockets.target

对应 service:

1
2
3
4
5
6
[Unit]
Description=Example socket service

[Service]
ExecStart=/usr/local/bin/example-server
StandardInput=socket

启用:

1
sudo systemctl enable --now example.socket

注意:

1
2
socket 激活要求服务程序适配这种启动方式。
普通 Web 服务不一定适合直接改成 socket 激活。

21. Path Unit

.path 用来监听文件或目录变化,并触发对应 .service

典型用途:

1
2
3
文件上传后触发处理
配置文件变化后触发同步
目录中出现新文件后启动任务

示例:

/etc/systemd/system/process-upload.path

1
2
3
4
5
6
7
8
9
[Unit]
Description=Watch upload directory

[Path]
PathExistsGlob=/data/upload/*.zip
Unit=process-upload.service

[Install]
WantedBy=multi-user.target

/etc/systemd/system/process-upload.service

1
2
3
4
5
6
[Unit]
Description=Process uploaded files

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/process-upload.sh

启用:

1
2
sudo systemctl daemon-reload
sudo systemctl enable --now process-upload.path

常见字段:

字段 含义
PathExists 某个路径存在时触发
PathExistsGlob 匹配 glob 时触发
PathChanged 文件内容变化后触发
PathModified 文件被修改时触发
DirectoryNotEmpty 目录非空时触发

22. Mount Unit

.mount 用来管理挂载点。

systemd 可以从 /etc/fstab 自动生成 mount unit,也可以手动写 .mount

注意命名规则:

1
2
挂载点 /data       -> data.mount
挂载点 /data/logs -> data-logs.mount

示例:

1
2
3
4
5
6
7
8
9
10
11
[Unit]
Description=Mount data disk

[Mount]
What=/dev/sdb1
Where=/data
Type=ext4
Options=defaults

[Install]
WantedBy=multi-user.target

启用:

1
2
sudo systemctl daemon-reload
sudo systemctl enable --now data.mount

实际运维里更常见的是先写 /etc/fstab

1
/dev/sdb1 /data ext4 defaults 0 2

然后用:

1
2
sudo systemctl daemon-reload
sudo mount -a

23. Override 覆盖配置

不要直接改系统包自带的 unit 文件。

推荐用 override:

1
sudo systemctl edit nginx.service

会生成类似:

1
/etc/systemd/system/nginx.service.d/override.conf

示例:

1
2
3
[Service]
Restart=on-failure
RestartSec=5

查看最终合并后的配置:

1
systemctl cat nginx.service

如果要清空某个列表型字段,需要先置空再重写。

例如重写 ExecStart

1
2
3
[Service]
ExecStart=
ExecStart=/usr/sbin/nginx -g 'daemon off;'

24. 安全建议

不要默认用 root 跑业务服务。

建议:

1
2
3
[Service]
User=app
Group=app

限制写目录:

1
ReadWritePaths=/opt/app /var/log/app

保护系统目录:

1
2
3
ProtectSystem=full
ProtectHome=true
PrivateTmp=true

限制权限提升:

1
NoNewPrivileges=true

建议思路:

1
2
服务需要什么权限,就只给什么权限。
不要为了省事让所有后台服务都用 root。

25. 排查流程

服务起不来时,按这个顺序排查:

1
2
3
4
systemctl status app.service
journalctl -u app.service -n 100 --no-pager
systemctl cat app.service
systemd-analyze verify /etc/systemd/system/app.service

重点看:

1
2
3
4
5
6
7
ExecStart 路径是否存在
脚本是否有执行权限
WorkingDirectory 是否存在
User 是否有权限
环境变量是否正确
是否忘记 daemon-reload
依赖 unit 是否正常

修改 unit 后:

1
2
sudo systemctl daemon-reload
sudo systemctl restart app.service

timer 不触发时,按这个顺序:

1
2
3
4
5
6
systemctl list-timers --all
systemctl status backup.timer
systemctl status backup.service
journalctl -u backup.timer
journalctl -u backup.service
systemd-analyze calendar '你的 OnCalendar 表达式'

重点看:

1
2
3
4
5
timer 是否 enable/start
OnCalendar 是否写错
Unit 指向是否正确
service 是否执行失败
Persistent 是否符合预期

26. 常见模板

Java 应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[Unit]
Description=App Manager
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=app
Group=app
WorkingDirectory=/home/app/app-manager
Environment="JAVA_OPTS=-Xms512m -Xmx1024m"
ExecStart=/usr/bin/java $JAVA_OPTS -jar app-manager.jar
Restart=on-failure
RestartSec=5
SuccessExitStatus=143

[Install]
WantedBy=multi-user.target

Watchdog

1
2
3
4
5
6
[Unit]
Description=WireGuard watchdog

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/wireguard-watchdog.sh
1
2
3
4
5
6
7
8
9
10
[Unit]
Description=Run WireGuard watchdog periodically

[Timer]
OnBootSec=1min
OnUnitActiveSec=2min
Unit=wireguard-watchdog.service

[Install]
WantedBy=timers.target

备份任务

1
2
3
4
5
6
7
[Unit]
Description=Backup database

[Service]
Type=oneshot
User=root
ExecStart=/usr/local/sbin/backup-db.sh
1
2
3
4
5
6
7
8
9
10
[Unit]
Description=Run database backup daily

[Timer]
OnCalendar=*-*-* 02:30:00
Persistent=true
RandomizedDelaySec=300

[Install]
WantedBy=timers.target

27. 速查表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 重新加载 unit 文件
sudo systemctl daemon-reload

# 启动服务
sudo systemctl start app.service

# 开机自启
sudo systemctl enable app.service

# 启用并立即启动
sudo systemctl enable --now app.service

# 查看服务状态
systemctl status app.service

# 查看日志
journalctl -u app.service -n 100 --no-pager

# 跟踪日志
journalctl -u app.service -f

# 查看所有 timer
systemctl list-timers --all

# 查看 unit 内容
systemctl cat app.service

# 检查 unit 文件语法
systemd-analyze verify /etc/systemd/system/app.service

# 查看失败 unit
systemctl --failed

28. 参考

1
2
3
4
5
6
7
8
9
man systemd.service
man systemd.timer
man systemd.unit
man systemd.target
man systemd.socket
man systemd.mount
man systemd.path
man systemctl
man journalctl

一句话总结:

1
service 管任务怎么运行,timer 管什么时候运行,target 管启动阶段和分组,socket/path/mount 则把端口、文件变化、挂载点也纳入 systemd 的统一管理。