8657 字
43 分钟

systemd 服务管理完全指南:从 systemctl 到自定义服务与定时任务 | 2026 最新实践

你有没有遇到过这些场景:

  • VPS 上跑的代理服务突然挂了,人不在电脑前,服务也不会自动重启
  • 写了个 Python 脚本想后台运行,用 nohup 跑起来后忘了 PID,找不回来也杀不掉
  • Nginx 改了配置,不确定 systemctl reloadsystemctl restart 有什么区别
  • 服务器日志散落在 /var/log/ 各个目录,找一个报错翻半天
  • crontab 定时任务不执行,但不知道哪里看错误日志

systemd 就是解决这些问题的核心工具——它是现代 Linux 的「大管家」,管理着从开机到关机、从网络到日志的一切。

如果你在用 Linux 服务器(Ubuntu 16.04+、CentOS 7+、Debian 8+),你已经在用 systemd 了。每次执行 systemctl start nginxsystemctl status docker,背后都是 systemd 在工作。

本文从零开始,系统讲解 systemd 的完整知识体系:

  • 基础操作:systemctl 启停服务、开机自启、状态查看
  • Unit 文件:service/timer/target 三大类型详解
  • 日志管理:journalctl 统一日志系统
  • 自定义服务:编写生产级 Service 文件
  • 定时任务:systemd timer 替代 crontab
  • 资源控制:CPU/内存/IO 限制
  • 安全加固:服务隔离与权限控制
  • 故障排查:常见问题与调试技巧

一、systemd 基础概念#

1.1 什么是 systemd#

systemd 是现代 Linux 的 init 系统(PID 1)和服务管理器。它负责:

  • 系统启动——并行启动服务,比传统 SysVinit 快 3-5 倍
  • 服务管理——启动、停止、重启、监控所有系统服务
  • 依赖管理——自动处理服务间的启动顺序和依赖
  • 日志收集——journald 统一收集所有服务日志
  • 网络管理——systemd-networkd 管理网络配置
  • 定时任务——systemd-timer 替代 crontab
  • 资源控制——cgroups 限制服务的 CPU/内存/IO

1.2 systemd vs 传统 init#

特性SysVinitsystemd
启动方式串行启动并行启动
依赖管理手动编号 S01, S02自动 After/Requires
服务管理service nginx startsystemctl start nginx
配置文件/etc/init.d/ 脚本/etc/systemd/system/ .service
日志各服务各自管理journalctl 统一管理
定时任务crontabsystemd timer
资源控制不支持cgroups 原生支持
服务监控不监控自动重启崩溃服务
状态查询不支持systemctl status 实时状态

1.3 核心组件#

systemd
├── systemd (PID 1) # 主进程,系统初始化
├── systemctl # 服务管理命令
├── journalctl # 日志查看命令
├── journald # 日志收集守护进程
├── networkd # 网络管理
├── resolved # DNS 解析
├── timesyncd # 时间同步
├── logind # 用户会话管理
└── cgroups # 资源控制

1.4 验证 systemd#

Terminal window
# 检查 systemd 是否是 PID 1
ps -p 1 -o comm=
# 输出: systemd
# 查看 systemd 版本
systemctl --version
# systemd 256 (256.x-1)
# 检查系统是否以 systemd 启动
if [ -d /run/systemd/system ]; then
echo "systemd is running"
fi

二、systemctl 服务管理#

2.1 服务生命周期管理#

Terminal window
# === 启动 / 停止 / 重启 ===
systemctl start nginx # 启动服务
systemctl stop nginx # 停止服务
systemctl restart nginx # 重启服务(先停再启)
systemctl reload nginx # 重新加载配置(不停服务)
systemctl try-restart nginx # 仅在运行时才重启(未运行不启动)
systemctl reload-or-restart nginx # 优先 reload,不支持则 restart
# === reload vs restart ===
# reload:服务进程不重启,只重新读取配置(零停机)
# restart:杀掉进程重新启动(有短暂中断)
# Nginx 支持 reload → 用 reload
# Python 脚本不支持 reload → 用 restart
# === 开机自启 ===
systemctl enable nginx # 设置开机自启
systemctl disable nginx # 取消开机自启
systemctl enable --now nginx # 设置开机自启并立即启动
systemctl disable --now nginx # 取消开机自启并立即停止
# enable 原理:创建符号链接
# /etc/systemd/system/multi-user.target.wants/nginx.service
# → /lib/systemd/system/nginx.service

2.2 查看服务状态#

Terminal window
# === 基础状态 ===
systemctl status nginx # 详细状态(含最近日志)
systemctl is-active nginx # 是否运行中 → active / inactive
systemctl is-enabled nginx # 是否开机自启 → enabled / disabled
systemctl is-failed nginx # 是否失败 → failed / success
# === 批量查看 ===
systemctl list-units --type=service # 所有已加载的 service
systemctl list-units --type=service --state=running # 运行中的服务
systemctl list-units --type=service --state=failed # 失败的服务
systemctl list-unit-files --type=service # 所有 service 文件
systemctl list-units --type=service --state=running | head -20 # 前 20 个
# === 查看依赖关系 ===
systemctl list-dependencies nginx # nginx 依赖什么
systemctl list-dependencies --reverse nginx # 谁依赖 nginx
# === 查看服务属性 ===
systemctl show nginx # 所有属性
systemctl show nginx -p ExecStart # 启动命令
systemctl show nginx -p MainPID # 主进程 PID
systemctl show nginx -p ActiveState # 激活状态
systemctl show nginx -p MemoryCurrent # 当前内存使用
systemctl show nginx -p CPUUsageNSec # CPU 使用时间

2.3 status 输出详解#

Terminal window
$ systemctl status nginx
nginx.service - The nginx HTTP and reverse proxy server
Loaded: loaded (/lib/systemd/system/nginx.service; enabled; preset: enabled)
Active: active (running) since Tue 2026-07-08 10:30:45 CST; 2h 15min ago
Duration: 2h 15min 45s
Docs: man:nginx(8)
Main PID: 12345 (nginx)
Tasks: 3 (limit: 4915)
Memory: 5.2M (CPU peak: 8.1M)
CPU: 320ms
CGroup: /system.slice/nginx.service
├─12345 "nginx: master process /usr/sbin/nginx -g 'daemon off;'"
└─12346 "nginx: worker process"
Jul 08 10:30:45 server systemd[1]: Starting The nginx HTTP and reverse proxy server...
Jul 08 10:30:45 server systemd[1]: Started The nginx HTTP and reverse proxy server.
Jul 08 10:35:20 server nginx[12345]: ... reload signal received

逐行解读:

含义
LoadedUnit 文件位置;enabled=开机自启
Active运行状态 + 持续时间
Main PID主进程 PID
Tasks进程数(cgroup 内)
Memory内存使用量
CPU累计 CPU 时间
CGroup控制组内的进程树
日志行最近 10 条日志

2.4 服务状态速查表#

状态含义说明
active (running)正在运行服务正常运行中
active (exited)已执行完毕一次性配置任务,执行完就结束
active (waiting)等待中timer 等待触发时间
inactive (dead)已停止服务未运行
failed失败服务崩溃或启动失败
activating启动中正在启动
deactivating停止中正在停止
reloading重载中正在重新加载配置

2.5 系统级操作#

Terminal window
# === 系统电源管理 ===
systemctl poweroff # 关机
systemctl reboot # 重启
systemctl suspend # 挂起(睡眠)
systemctl hibernate # 休眠(写入磁盘)
systemctl hybrid-sleep # 混合睡眠
# === 重新加载配置 ===
systemctl daemon-reload # 重新加载所有 Unit 文件(改完 .service 必须执行)
systemctl daemon-reexec # 重新执行 systemd 自身(极少用)
# === 重置失败状态 ===
systemctl reset-failed # 清除所有 failed 状态
systemctl reset-failed nginx # 清除指定服务的 failed 状态
# === 切换运行目标 ===
systemctl isolate multi-user.target # 切换到多用户模式(无图形界面)
systemctl isolate graphical.target # 切换到图形界面模式
systemctl rescue # 进入救援模式
systemctl emergency # 进入紧急模式

三、Unit 文件详解#

3.1 Unit 文件位置#

目录用途优先级
/etc/systemd/system/管理员自定义最高(推荐放这里)
/run/systemd/system/运行时临时
/lib/systemd/system//usr/lib/systemd/system/软件包安装

优先级规则: 同名文件,高优先级目录覆盖低优先级。自定义服务放 /etc/systemd/system/,修改系统服务用 systemctl edit

3.2 Unit 类型一览#

类型扩展名用途示例
service.service系统服务nginx.service
target.target服务组/运行级别multi-user.target
timer.timer定时任务logrotate.timer
socket.socket套接字激活docker.socket
mount.mount挂载点mnt-data.mount
automount.automount自动挂载mnt-data.automount
swap.swap交换分区swapfile.swap
device.device设备dev-sda1.device
path.path路径监控cups.path
slice.slice资源分配组user.slice

3.3 Service 文件结构#

一个完整的 Service 文件包含多个 Section:

[Unit]
Description=My Web Application
Documentation=https://example.com/docs
After=network-online.target
Wants=network-online.target
Requires=postgresql.service
Conflicts=old-app.service
[Service]
Type=simple
User=appuser
Group=appuser
WorkingDirectory=/opt/myapp
Environment=NODE_ENV=production
Environment=PORT=3000
EnvironmentFile=-/etc/myapp/env
ExecStart=/usr/bin/node /opt/myapp/server.js
ExecStartPre=/usr/bin/mkdir -p /var/run/myapp
ExecStartPost=/usr/bin/sh -c 'echo "App started" > /var/log/myapp-start.log'
ExecStop=/usr/bin/kill -TERM $MAINPID
ExecReload=/usr/bin/kill -HUP $MAINPID
Restart=always
RestartSec=5s
TimeoutStartSec=30s
TimeoutStopSec=15s
RuntimeMaxSec=7d
LimitNOFILE=65536
LimitNPROC=4096
# 资源限制
MemoryMax=512M
CPUQuota=100%
IOWeight=500
# 安全加固
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/myapp /var/log/myapp
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_BIND_SERVICE
[Install]
WantedBy=multi-user.target

3.4 [Unit] 段详解#

[Unit]
Description=My App # 人类可读描述
Documentation=https://example.com # 文档链接
After=network-online.target # 在哪些服务之后启动(不强制等待)
Before=nginx.service # 在哪些服务之前启动
Requires=postgresql.service # 强依赖:必须同时运行
Wants=network-online.target # 弱依赖:最好一起启动
Requisite=postgresql.service # 必须已经在运行(不会触发启动)
Conflicts=old-app.service # 互斥:不能同时运行
PartOf=myapp-stack.target # 属于某个组,组重启时跟着重启
BindsTo=postgresql.service # 绑定:依赖挂了自己也挂

After vs Requires 区别:

配置作用
After=network.target如果 network 要启动,等它启动完再启动我
Requires=network.target我需要 network,拉起我的同时拉起它
After= + Requires=我需要 network,而且等它启动完再启动我(最常用组合)
Wants=和 Requires 类似,但依赖失败不影响我启动
# 常见组合:需要网络的服务
After=network-online.target
Wants=network-online.target

3.5 [Service] 段详解#

Type(服务类型)#

Type含义适用场景
simple(默认)ExecStart 启动的进程就是主进程前台程序(推荐)
forking进程会 fork,父进程退出,子进程后台运行传统守护进程
oneshot执行一次就完成一次性脚本
exec类似 simple,但确保 ExecStart 二进制存在更严格的 simple
notify服务通过 sd_notify 通知 systemd 就绪支持 systemd 的服务
idle类似 simple,但等所有任务完成才启动不紧急的控制台服务
# Type=simple:最常用,ExecStart 的进程就是服务
[Service]
Type=simple
ExecStart=/usr/bin/python3 /opt/app/server.py
# Type=forking:传统守护进程(如 sshd)
[Service]
Type=forking
PIDFile=/run/sshd.pid
ExecStart=/usr/sbin/sshd
# Type=oneshot:一次性任务
[Service]
Type=oneshot
ExecStart=/opt/app/init.sh
RemainAfterExit=yes # 脚本执行完仍标记为 active
# Type=notify:服务自己通知就绪
[Service]
Type=notify
ExecStart=/usr/sbin/mysqld
NotifyAccess=main

Exec* 指令#

# ExecStart:启动命令(必须用绝对路径)
ExecStart=/usr/bin/nginx -g 'daemon off;'
# 多行 ExecStart(Type=oneshot 时按顺序执行)
ExecStart=/usr/bin/mkdir -p /var/run/myapp
ExecStart=/usr/bin/chown appuser:appuser /var/run/myapp
# ExecStartPre:启动前执行
ExecStartPre=/usr/bin/rm -f /var/run/myapp.pid
ExecStartPre=/usr/bin/test -f /opt/myapp/config.json
# ExecStartPre 用 - 前缀忽略错误
ExecStartPre=-/usr/bin/mkdir -p /var/run/myapp # 即使失败也不阻止启动
# ExecStartPost:启动后执行
ExecStartPost=/usr/bin/sh -c 'echo "Started at $(date)" >> /var/log/myapp.log'
# ExecStop:停止命令(默认发 SIGTERM)
ExecStop=/usr/bin/nginx -s stop
# ExecStopPost:停止后执行
ExecStopPost=/usr/bin/rm -f /var/run/myapp.pid
# ExecReload:重新加载命令
ExecReload=/usr/bin/nginx -s reload

Restart(自动重启)#

# Restart 策略
Restart=no # 默认,不自动重启
Restart=on-failure # 非零退出码时重启
Restart=on-success # 零退出码时重启
Restart=always # 总是重启(无论退出码)
Restart=on-abnormal # 信号杀掉或超时时重启
Restart=on-watchdog # 看门狗超时时重启
# 重启间隔
RestartSec=5s # 每次重启间隔 5 秒
RestartSec=3 10 # 随机 3-10 秒(防止雪崩)
# 重启次数限制
StartLimitBurst=5 # 最多重启 5 次
StartLimitIntervalSec=60 # 60 秒内
# 达到限制后的行为
# 默认:停止重启,状态变为 failed
# 可设置 StartLimitAction=reboot 重启整机

Restart 策略选择指南:

场景推荐 Restart原因
Web 服务器always保证服务始终可用
代理服务always崩溃自动恢复
一次性脚本no不需要重启
可能配置错误on-failure配置错误时不无限重启
数据库on-failure避免数据损坏时反复重启

Environment(环境变量)#

# 直接设置
Environment=NODE_ENV=production
Environment=PORT=3000
Environment="API_KEY=sk-xxx" # 含特殊字符用引号
# 从文件读取
EnvironmentFile=/etc/myapp/env # 文件内容:KEY=VALUE
EnvironmentFile=-/etc/myapp/env.local # - 前缀:文件不存在不报错
# env 文件格式
# PORT=3000
# NODE_ENV=production
# DATABASE_URL=postgresql://user:pass@localhost:5432/db

超时控制#

TimeoutStartSec=30s # 启动超时(默认 90s)
TimeoutStopSec=15s # 停止超时(默认 90s,超时后 SIGKILL)
TimeoutSec=30s # 同时设置启动和停止超时
RuntimeMaxSec=7d # 最大运行时间(到期自动停止)
WatchdogSec=30s # 看门狗(服务必须定期 sd_notify,否则重启)

3.6 [Install] 段详解#

[Install]
WantedBy=multi-user.target # 开机自启时挂到哪个 target
# 等价于 systemctl enable 时创建符号链接到 multi-user.target.wants/
# 其他选项
RequiredBy=nginx.service # 被哪些服务需要
Alias=myapp.service # 别名
Also=myapp-timer.timer # enable 时同时 enable 这个

常用 WantedBy:

Target含义何时启动
multi-user.target多用户模式(命令行)开机自动
graphical.target图形界面模式开机自动(桌面系统)
default.target默认目标等同 multi-user 或 graphical
network-online.target网络就绪网络可用后
timers.target定时任务timer 专用

四、Target 与运行级别#

4.1 Target 基础#

Target 是一组 Unit 的集合,类似于传统 init 的运行级别:

传统运行级别systemd Target含义
runlevel 0poweroff.target关机
runlevel 1rescue.target单用户/救援模式
runlevel 2multi-user.target多用户(无网络)
runlevel 3multi-user.target多用户(有网络,命令行)
runlevel 4未使用
runlevel 5graphical.target图形界面
runlevel 6reboot.target重启

4.2 Target 操作#

Terminal window
# 查看当前默认目标
systemctl get-default
# 输出: multi-user.target
# 设置默认目标
systemctl set-default graphical.target
systemctl set-default multi-user.target
# 查看目标包含的服务
systemctl list-dependencies multi-user.target
# 临时切换目标
systemctl isolate rescue.target # 进入救援模式
systemctl isolate emergency.target # 进入紧急模式
# 查看所有 target
systemctl list-units --type=target
systemctl list-unit-files --type=target

4.3 常用 Target#

Terminal window
# 网络相关
network.target # 网络栈初始化(不代表网络已通)
network-online.target # 网络真正可用(等待配置完成)
network-pre.target # 网络配置前
network-manager.target # NetworkManager 就绪
# 存储相关
local-fs.target # 本地文件系统挂载
remote-fs.target # 远程文件系统挂载
swap.target # 交换分区
# 系统相关
basic.target # 基础系统就绪
multi-user.target # 多用户模式就绪
graphical.target # 图形界面就绪
# 定时任务
timers.target # 所有 timer 就绪

重要: network.targetnetwork-online.target 不同。network.target 在网络栈初始化时就激活,但此时网络可能还没通。需要网络真正可用的服务用 After=network-online.target + Wants=network-online.target


五、journalctl 日志管理#

5.1 基础查询#

Terminal window
# === 按服务查看 ===
journalctl -u nginx # 查看 nginx 全部日志
journalctl -u nginx -u php-fpm # 同时查看多个服务
journalctl -u nginx.service # 完整名称
# === 实时跟踪 ===
journalctl -u nginx -f # 类似 tail -f
journalctl -f # 跟踪所有日志
journalctl -f -p err # 只跟踪错误级别
# === 按时间过滤 ===
journalctl --since today # 今天的日志
journalctl --since yesterday # 昨天的日志
journalctl --since "2026-07-08" # 指定日期
journalctl --since "2026-07-08 10:00" --until "2026-07-08 12:00"
journalctl --since "1 hour ago" # 1 小时前
journalctl --since "30 min ago" # 30 分钟前
# === 按启动过滤 ===
journalctl -b # 当前启动的日志
journalctl -b -1 # 上一次启动的日志
journalctl -b -2 # 上上次启动
journalctl --list-boots # 列出所有启动记录
# === 按优先级过滤 ===
journalctl -p err # 错误级别及以上
journalctl -p warning # 警告级别及以上
journalctl -p debug # 所有级别
journalctl -p 3 # 用数字:0-7

日志优先级:

数字关键字含义
0emerg紧急:系统不可用
1alert警报:必须立即处理
2crit严重:严重错误
3err错误
4warning警告
5notice通知
6info信息
7debug调试

5.2 高级查询#

Terminal window
# === 按可执行文件过滤 ===
journalctl /usr/bin/nginx # nginx 进程的日志
journalctl /usr/sbin/sshd # sshd 进程的日志
# === 按 PID 过滤 ===
journalctl _PID=12345
# === 按用户过滤 ===
journalctl _UID=1000 # 指定用户的日志
# === 按控制组过滤 ===
journalctl _SYSTEMD_UNIT=nginx.service
# === 组合过滤 ===
journalctl -u nginx -p err --since today # 今天 nginx 的错误
journalctl -u nginx -p warning --since "1h ago" # 最近 1 小时的警告
journalctl -u docker --since today -n 50 # 今天 docker 最近 50 条
# === 输出格式 ===
journalctl -u nginx -n 20 # 最近 20 条
journalctl -u nginx --no-pager # 不分页(适合管道)
journalctl -u nginx -o cat # 只输出消息内容(无元数据)
journalctl -u nginx -o json # JSON 格式
journalctl -u nginx -o json-pretty # 美化 JSON
journalctl -u nginx -o verbose # 详细字段
journalctl -u nginx --no-hostname # 不显示主机名
# === 反向输出(最新在上) ===
journalctl -u nginx -r # 反向(最新在前)
journalctl -u nginx -rn 20 # 反向最近 20 条

5.3 日志维护#

Terminal window
# === 查看日志大小 ===
journalctl --disk-usage
# Archived and active journals take up 500M in the file system.
# === 清理日志 ===
# 按大小清理
journalctl --vacuum-size=100M # 只保留最近 100M
journalctl --vacuum-size=500M
# 按时间清理
journalctl --vacuum-time=7d # 只保留最近 7 天
journalctl --vacuum-time="2 weeks"
# 按文件数清理
journalctl --vacuum-files=3 # 只保留最近 3 个日志文件
# === 永久存储 ===
# 默认日志存在 /run/log/journal/(重启后丢失)
# 开启永久存储:
sudo mkdir -p /var/log/journal/
sudo systemctl restart systemd-journald
# === 配置 journald ===
sudo vim /etc/systemd/journald.conf
# /etc/systemd/journald.conf 关键配置
[Journal]
Storage=persistent # 永久存储到 /var/log/journal/
Compress=yes # 压缩旧日志
SystemMaxUse=500M # 日志最大 500M
SystemMaxFileSize=50M # 单个文件最大 50M
MaxRetentionSec=1month # 保留 1 个月
RateLimitIntervalSec=30s # 限速:30 秒内
RateLimitBurst=10000 # 最多 10000 条
Terminal window
# 修改后重载
sudo systemctl restart systemd-journald

5.4 日志实战#

Terminal window
# === 排查服务启动失败 ===
systemctl status nginx # 看状态和最近日志
journalctl -u nginx -b -p err # 本次启动的错误日志
journalctl -u nginx --since "5 min ago" # 最近 5 分钟
# === 排查系统启动慢 ===
systemd-analyze # 总启动时间
systemd-analyze blame # 各服务启动耗时
systemd-analyze critical-chain # 关键路径
systemd-analyze plot > boot.svg # 生成启动时间线图
# === 排查 OOM(内存不足杀进程)===
journalctl -k -g "Killed process" # 内核日志中搜索 OOM
journalctl --since today -p err | grep -i oom
# === 导出日志给他人分析 ===
journalctl -u nginx --since "1 hour ago" --no-pager > nginx-log.txt
journalctl -u nginx -o json-pretty > nginx-log.json

六、编写自定义服务#

6.1 Python Web 服务#

Terminal window
sudo vim /etc/systemd/system/myapp.service
[Unit]
Description=My Python Web Application
Documentation=https://github.com/myorg/myapp
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=appuser
Group=appuser
WorkingDirectory=/opt/myapp
Environment="PYTHONUNBUFFERED=1"
Environment="FLASK_ENV=production"
EnvironmentFile=/etc/myapp/env
ExecStart=/opt/myapp/venv/bin/python /opt/myapp/app.py
Restart=always
RestartSec=5s
# 资源限制
MemoryMax=512M
CPUQuota=200%
# 安全加固
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ReadWritePaths=/opt/myapp/data /var/log/myapp
# 日志
StandardOutput=journal
StandardError=journal
SyslogIdentifier=myapp
[Install]
WantedBy=multi-user.target
Terminal window
# 创建用户和目录
sudo useradd -r -s /bin/false appuser
sudo mkdir -p /opt/myapp/data /var/log/myapp
sudo chown -R appuser:appuser /opt/myapp /var/log/myapp
# 部署服务
sudo systemctl daemon-reload
sudo systemctl enable --now myapp
sudo systemctl status myapp
# 查看日志
journalctl -u myapp -f

6.2 Node.js 服务#

[Unit]
Description=Node.js API Server
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=nodeapp
Group=nodeapp
WorkingDirectory=/opt/api-server
Environment=NODE_ENV=production
Environment=PORT=3000
ExecStart=/usr/bin/node /opt/api-server/dist/index.js
Restart=always
RestartSec=3s
# 优雅停止
KillSignal=SIGINT
TimeoutStopSec=10s
# 文件描述符限制
LimitNOFILE=65536
# 安全加固
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/opt/api-server/logs /opt/api-server/uploads
[Install]
WantedBy=multi-user.target

6.3 Docker Compose 服务#

[Unit]
Description=Docker Compose Application Stack
Requires=docker.service
After=docker.service
[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/opt/myapp
ExecStart=/usr/bin/docker compose up -d
ExecStop=/usr/bin/docker compose down
ExecReload=/usr/bin/docker compose restart
TimeoutStartSec=0
[Install]
WantedBy=multi-user.target
Terminal window
# 使用
sudo systemctl start myapp-docker # docker compose up -d
sudo systemctl stop myapp-docker # docker compose down
sudo systemctl reload myapp-docker # docker compose restart

6.4 代理服务(V2Ray/Xray)#

[Unit]
Description=Xray Service
Documentation=https://xtls.github.io
After=network-online.target nss-lookup.target
Wants=network-online.target
[Service]
Type=simple
User=nobody
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
NoNewPrivileges=true
ExecStart=/usr/local/bin/xray run -config /usr/local/etc/xray/config.json
Restart=on-failure
RestartPreventExitStatus=23
LimitNPROC=10000
LimitNOFILE=1000000
[Install]
WantedBy=multi-user.target

6.5 Shell 脚本服务#

[Unit]
Description=Log Rotation Custom Script
After=network-online.target
[Service]
Type=oneshot
ExecStart=/opt/scripts/log-rotate.sh
RemainAfterExit=yes
# 超时保护
TimeoutStartSec=300
[Install]
WantedBy=multi-user.target

6.6 服务部署检查清单#

Terminal window
# 1. 验证 Unit 文件语法
systemd-analyze verify /etc/systemd/system/myapp.service
# 2. 重新加载
systemctl daemon-reload
# 3. 启动并查看状态
systemctl start myapp
systemctl status myapp
# 4. 检查日志
journalctl -u myapp -n 20 --no-pager
# 5. 设置开机自启
systemctl enable myapp
# 6. 验证开机自启
systemctl is-enabled myapp
# 7. 测试自动重启
systemctl kill myapp # 杀掉进程
sleep 6 # 等待 RestartSec
systemctl status myapp # 应该自动恢复为 active

七、systemctl edit 修改系统服务#

7.1 覆盖目录#

systemctl edit 创建覆盖文件,不修改原始 Unit 文件:

Terminal window
# 编辑服务的覆盖配置
sudo systemctl edit nginx
# 这会创建:
# /etc/systemd/system/nginx.service.d/override.conf
# 编辑器中写入覆盖内容:
[Service]
Restart=always
RestartSec=3s
LimitNOFILE=65536
# 保存后自动 reload
sudo systemctl daemon-reload
sudo systemctl restart nginx

7.2 常见覆盖场景#

Terminal window
# 场景1:给 Nginx 增加文件描述符限制
sudo systemctl edit nginx
[Service]
LimitNOFILE=65536
Terminal window
# 场景2:让 Docker 不自动重启
sudo systemctl edit docker
[Service]
Restart=no
Terminal window
# 场景3:修改 SSH 端口(不推荐改 service 文件,改 sshd_config 更好)
sudo systemctl edit sshd
[Service]
ExecStart=
ExecStart=/usr/sbin/sshd -D -p 2222

注意: 清空 ExecStart= 必须先写一个空值,再写新值。因为 ExecStart 是单值指令,需要先清除原值。

Terminal window
# 场景4:给服务添加环境变量
sudo systemctl edit myapp
[Service]
Environment="DEBUG=true"
Environment="LOG_LEVEL=debug"

7.3 查看完整配置(含覆盖)#

Terminal window
# 查看合并后的完整配置
systemctl cat nginx
# 查看实际生效的属性
systemctl show nginx -p Restart
systemctl show nginx -p LimitNOFILE

7.4 撤销覆盖#

Terminal window
# 撤销 override
sudo systemctl revert nginx
# 或手动删除
sudo rm -rf /etc/systemd/system/nginx.service.d/
sudo systemctl daemon-reload
sudo systemctl restart nginx

八、systemd Timer 定时任务#

8.1 Timer vs Crontab#

特性crontabsystemd timer
最小精度1 分钟1 秒甚至毫秒
依赖管理After/Wants/Requires
日志手动重定向自动 journalctl
错过执行不补执行Persistent=true 可补执行
资源限制不支持cgroups 限制
随机延迟不支持(需 sleep)RandomizedDelaySec
条件触发不支持ConditionPathExists 等
配置复杂度简单较复杂(两个文件)

8.2 Timer 文件结构#

Timer 需要两个文件:一个 .timer 定义何时触发,一个 .service 定义执行什么。

/etc/systemd/system/backup.timer
[Unit]
Description=Daily Backup Timer
[Timer]
OnCalendar=daily # 每天触发
Persistent=true # 错过了补执行
RandomizedDelaySec=5m # 随机延迟 0-5 分钟
[Install]
WantedBy=timers.target
/etc/systemd/system/backup.service
[Unit]
Description=Daily Backup Service
[Service]
Type=oneshot
ExecStart=/opt/scripts/backup.sh
User=backup
Terminal window
# 启用
sudo systemctl daemon-reload
sudo systemctl enable --now backup.timer
# 查看 timer 状态
systemctl list-timers
systemctl list-timers --all
systemctl status backup.timer

8.3 触发时间配置#

[Timer]
# === 日历时间 ===
OnCalendar=daily # 每天 00:00
OnCalendar=weekly # 每周一 00:00
OnCalendar=monthly # 每月 1 日 00:00
OnCalendar=*-*-* 02:00:00 # 每天 02:00
OnCalendar=*-*-* 02,14:00:00 # 每天 02:00 和 14:00
OnCalendar=Mon *-*-* 03:00:00 # 每周一 03:00
OnCalendar=*-*-01..07 03:00:00 # 每月第一个周日 03:00
# === 相对时间 ===
OnBootSec=2min # 开机后 2 分钟
OnStartupSec=5min # systemd 启动后 5 分钟
# === 活动后触发 ===
OnUnitActiveSec=1h # 上次执行后 1 小时
OnUnitInactiveSec=30min # 上次结束后 30 分钟
# === 精确间隔 ===
OnUnitActiveSec=30s # 每 30 秒(最小精度)
# === 随机延迟 ===
RandomizedDelaySec=5m # 随机延迟 0-5 分钟(防雪崩)
# === 固定延迟 ===
FixedRandomDelay=true # 使用固定随机值(每次相同)
# === 错过补执行 ===
Persistent=true # 系统关机期间错过的,开机后补执行
# === 精度 ===
AccuracySec=1s # 触发精度(默认 1 分钟)

8.4 实用 Timer 示例#

/etc/systemd/system/backup.timer
# === 示例1:每天凌晨 3 点备份 ===
[Unit]
Description=Daily Backup at 3 AM
[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
RandomizedDelaySec=5m
[Install]
WantedBy=timers.target
# === 示例2:每 30 分钟同步 ===
# /etc/systemd/system/sync.timer
[Unit]
Description=Sync Every 30 Minutes
[Timer]
OnBootSec=5min
OnUnitActiveSec=30min
AccuracySec=1s
[Install]
WantedBy=timers.target
# === 示例3:工作日 9 点发送报告 ===
# /etc/systemd/system/report.timer
[Unit]
Description=Weekday Report at 9 AM
[Timer]
OnCalendar=Mon..Fri *-*-* 09:00:00
Persistent=true
[Install]
WantedBy=timers.target
# === 示例4:每 10 秒检查一次 ===
# /etc/systemd/system/health-check.timer
[Unit]
Description=Health Check Every 10 Seconds
[Timer]
OnBootSec=10s
OnUnitActiveSec=10s
AccuracySec=1s
[Install]
WantedBy=timers.target

8.5 从 crontab 迁移到 Timer#

Terminal window
# crontab 条目:
# 0 3 * * * /opt/scripts/backup.sh
# 迁移步骤:
# 1. 创建 service 文件
cat > /etc/systemd/system/backup.service << 'EOF'
[Unit]
Description=Backup Script
[Service]
Type=oneshot
ExecStart=/opt/scripts/backup.sh
User=backup
EOF
# 2. 创建 timer 文件
cat > /etc/systemd/system/backup.timer << 'EOF'
[Unit]
Description=Daily Backup
[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
[Install]
WantedBy=timers.target
EOF
# 3. 启用
systemctl daemon-reload
systemctl enable --now backup.timer

8.6 查看 Timer 状态#

Terminal window
# 查看所有 timer
systemctl list-timers
systemctl list-timers --all
# 输出示例:
# NEXT LEFT LAST PASSED UNIT ACTIVATES
# Tue 2026-07-08 03:00:00 CST 16h remaining Mon 2026-07-07 03:00:01 8h ago backup.timer backup.service
# 查看特定 timer
systemctl status backup.timer
systemctl list-timers backup.timer
# 查看 timer 触发历史
journalctl -u backup.timer
journalctl -u backup.service

九、资源控制#

9.1 CPU 限制#

[Service]
# CPU 配额(百分比,100% = 1 核)
CPUQuota=50% # 最多使用 0.5 核
CPUQuota=200% # 最多使用 2 核
CPUQuota=300% # 最多使用 3 核
# CPU 权重(1-10000,默认 100)
CPUWeight=500 # 比默认高 5 倍的优先级
# CPU 亲和性(绑定到特定核心)
CPUAffinity=0,1 # 只用 CPU0 和 CPU1
# CPU 调度策略
CPUSchedulingPolicy=fifo
CPUSchedulingPriority=50

9.2 内存限制#

[Service]
# 内存上限(硬限制,超过会被 OOM Kill)
MemoryMax=512M
MemoryMax=2G
# 内存软限制(超过会回收,但不杀进程)
MemoryHigh=400M
# 内存+Swap 总限制
MemorySwapMax=256M
# OOM 行为
OOMPolicy=stop # OOM 时停止服务(默认)
OOMPolicy=continue # OOM 时杀掉子进程但服务继续
OOMPolicy=kill # OOM 时杀掉整个 cgroup

9.3 IO 限制#

[Service]
# IO 权重(1-10000,默认 100)
IOWeight=500
# 读写速率限制
IOReadBandwidthMax=/dev/sda 10M # /dev/sda 上读速 10MB/s
IOWriteBandwidthMax=/dev/sda 10M # /dev/sda 上写速 10MB/s
# IOPS 限制
IOReadIOPSMax=/dev/sda 1000 # 1000 IOPS
IOWriteIOPSMax=/dev/sda 1000
# 设备访问限制
DeviceAllow=/dev/null rw # 只允许访问 /dev/null
DevicePolicy=closed # 禁止访问未列出的设备

9.4 文件描述符与进程数#

[Service]
# 文件描述符限制
LimitNOFILE=65536 # 最大打开文件数
LimitNPROC=4096 # 最大进程数
# 其他 ulimit
LimitCORE=infinity # core 文件大小
LimitFSIZE=infinity # 文件大小
LimitCPU=infinity # CPU 时间
LimitAS=infinity # 虚拟内存
LimitRSS=infinity # 常驻内存

9.5 查看资源使用#

Terminal window
# 查看服务资源使用
systemctl show nginx -p MemoryCurrent -p CPUUsageNSec -p TasksCurrent
# 使用 systemd-cgtop 实时监控
systemd-cgtop
# 类似 top,但按 cgroup 分组
# 查看控制组
systemd-cgls
systemd-cgls nginx.service
# 查看资源限制配置
systemctl show nginx -p MemoryMax -p CPUQuota -p LimitNOFILE

十、安全加固#

10.1 权限最小化#

[Service]
# 禁止获取新权限
NoNewPrivileges=true
# 临时文件私有化(/tmp 隔离)
PrivateTmp=true
# 禁止访问 /home
ProtectHome=true
# 禁止访问 /root
ProtectHome=read-only
# 文件系统保护
ProtectSystem=strict # 整个文件系统只读
ProtectSystem=full # /usr, /boot, /etc 只读
ProtectSystem=true # /usr, /boot 只读
# 允许写入的路径(配合 ProtectSystem=strict)
ReadWritePaths=/var/lib/myapp /var/log/myapp
# 设备访问限制
PrivateDevices=true # 禁止访问物理设备
# 网络隔离
PrivateNetwork=false # true=完全隔离网络(小心)
IPAddressDeny=any # 禁止所有网络
IPAddressAllow=127.0.0.1 # 只允许 localhost
# 内核模块
ProtectKernelModules=true # 禁止加载内核模块
# 内核日志
ProtectKernelLogs=true # 禁止访问内核日志
# 控制组
ProtectControlGroups=true # 禁止访问 cgroup 文件系统
# 时钟
ProtectClock=true # 禁止修改系统时钟
# hostname
ProtectHostname=true # 禁止修改 hostname

10.2 Capability 限制#

[Service]
# 丢弃所有 capabilities,只保留必要的
CapabilityBoundingSet=
# 只允许绑定 1024 以下端口
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_BIND_SERVICE
# 常用 capabilities:
# CAP_NET_BIND_SERVICE - 绑定 <1024 端口
# CAP_NET_RAW - 原始套接字(ping 等)
# CAP_SYS_ADMIN - 系统管理(功能强大,慎用)
# CAP_DAC_OVERRIDE - 绕过文件权限
# CAP_KILL - 杀进程
# CAP_CHOWN - chown
# CAP_SETUID - 切换用户

10.3 安全配置速查表#

配置作用影响
NoNewPrivileges=true禁止提权阻止 setuid 提权攻击
PrivateTmp=true/tmp 隔离防止临时文件竞争
ProtectSystem=strict文件系统只读防止文件篡改
ProtectHome=true禁止访问 /home隔离用户数据
PrivateDevices=true禁止物理设备防止设备访问
ProtectKernelModules=true禁止加载模块防止内核攻击
ProtectClock=true禁止改时钟防止时间篡改
ProtectControlGroups=true禁止改 cgroup防止资源逃逸
RestrictAddressFamilies=AF_INET AF_UNIX限制 socket 类型减少攻击面
RestrictNamespaces=true禁止创建 namespace防止容器逃逸
RestrictRealtime=true禁止实时调度防止 DoS
RestrictSUIDSGID=true禁止 setuid/setgid防止提权
LockPersonality=true锁定执行域防止 ABI 切换
MemoryDenyWriteExecute=true禁止可写可执行内存防止代码注入
SystemCallFilter=@system-service限制系统调用大幅减少攻击面

10.4 安全加固模板#

[Service]
# === 最小权限 ===
User=myapp
Group=myapp
NoNewPrivileges=true
# === 文件系统隔离 ===
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
ReadWritePaths=/var/lib/myapp /var/log/myapp
# === 设备与内核隔离 ===
PrivateDevices=true
ProtectKernelModules=true
ProtectKernelLogs=true
ProtectControlGroups=true
ProtectClock=true
ProtectHostname=true
# === 网络限制 ===
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
# === Namespace 限制 ===
RestrictNamespaces=true
RestrictRealtime=true
RestrictSUIDSGID=true
LockPersonality=true
# === 内存保护 ===
MemoryDenyWriteExecute=true
# === 系统调用过滤 ===
SystemCallFilter=@system-service
SystemCallFilter=~@privileged @resources
# === Capability ===
CapabilityBoundingSet=
AmbientCapabilities=
# === 日志 ===
StandardOutput=journal
StandardError=journal

10.5 安全审计#

Terminal window
# 使用 systemd-analyze security 检查安全分数
systemd-analyze security nginx
# 输出示例:
# → Overall exposure level for nginx.service: 4.2 OK ✓
# ...
# PrivateTmp= no → 0.1
# ProtectSystem= no → 0.2
# ...
# 检查所有服务
systemd-analyze security

十一、Socket 激活#

Socket 激活是 systemd 的特色功能——服务在有人连接时才启动,空闲时不占资源。

11.1 工作原理#

传统方式: Socket 激活:
1. 开机启动服务 1. 开机时 systemd 创建 socket
2. 服务持续运行 2. 服务不启动(不占资源)
3. 请求到达 → 服务处理 3. 请求到达 → systemd 启动服务 → 转交 socket
4. 空闲时也运行 4. 空闲时不运行

11.2 Socket 文件#

/etc/systemd/system/myapp.socket
[Unit]
Description=My App Socket
[Socket]
ListenStream=3000 # 监听 TCP 3000 端口
# ListenStream=/run/myapp.sock # 或监听 Unix Socket
Accept=no # no=单进程处理所有连接, yes=每连接一进程
[Install]
WantedBy=sockets.target
/etc/systemd/system/myapp.service
# 注意:ExecStart 不需要自己监听端口
# 服务通过 systemd 传入的 socket fd 接收连接
[Service]
Type=simple
ExecStart=/usr/bin/myapp
Terminal window
# 启用
sudo systemctl enable --now myapp.socket
# 服务会在第一次连接时自动启动

11.3 Docker Socket 激活#

Terminal window
# Docker 默认使用 socket 激活
sudo systemctl enable docker.socket
sudo systemctl start docker.socket
# 只有执行 docker 命令时,dockerd 才真正启动
# 空闲时不占内存

十二、systemd-analyze 调试工具#

12.1 启动分析#

Terminal window
# 总启动时间
systemd-analyze
# Startup finished in 5.123s (kernel) + 8.456s (userspace) = 13.579s
# 各服务启动耗时(从慢到快)
systemd-analyze blame
# 8.123s docker.service
# 3.456s networkd-dispatcher.service
# 2.789s snapd.service
# 1.234s nginx.service
# ...
# 关键路径(最慢的启动链)
systemd-analyze critical-chain
# The time when unit became active or started is printed after...
# └─docker.service @3.456s +8.123s
# └─network-online.target @3.450s
# └─network.target @3.448s
# └─...
# 生成启动时间线 SVG 图
systemd-analyze plot > /tmp/boot.svg
# 用浏览器打开查看可视化时间线
# 生成安全分析图
systemd-analyze security

12.2 配置验证#

Terminal window
# 验证 Unit 文件语法
systemd-analyze verify /etc/systemd/system/myapp.service
# 验证所有 Unit 文件
systemd-analyze verify
# 分析 Unit 依赖
systemd-analyze dot nginx.service | dot -Tsvg > /tmp/deps.svg

12.3 日志级别#

Terminal window
# 设置 systemd 调试日志级别
sudo systemctl log-level debug
# ... 操作 ...
sudo systemctl log-level info # 恢复
# 查看调试日志
journalctl -u systemd -p debug --since "5 min ago"

十三、常见问题排查#

Q1: 服务启动失败#

Terminal window
# 步骤1:查看状态和错误信息
systemctl status myapp
# 步骤2:查看详细日志
journalctl -u myapp -b --no-pager | tail -30
# 步骤3:验证 Unit 文件语法
systemd-analyze verify /etc/systemd/system/myapp.service
# 常见原因:
# 1. ExecStart 路径错误 → 用绝对路径
# 2. 权限问题 → 检查 User/Group 和文件权限
# 3. 环境变量缺失 → 检查 EnvironmentFile
# 4. 依赖未启动 → 检查 After/Requires
# 5. 端口被占用 → ss -tlnp | grep :PORT

Q2: 改了 .service 文件不生效#

Terminal window
# 必须重新加载
sudo systemctl daemon-reload
# 然后重启服务
sudo systemctl restart myapp
# 如果还是不生效,检查是否有 override
systemctl cat myapp

Q3: 服务不停重启#

Terminal window
# 查看重启次数和限制
systemctl show myapp -p NRestarts -p StartLimitBurst -p StartLimitIntervalUSec
# 如果达到限制,服务会变为 failed
# 重置失败状态
sudo systemctl reset-failed myapp
sudo systemctl start myapp
# 调整重启限制
[Service]
StartLimitBurst=10
StartLimitIntervalSec=60
RestartSec=10s

Q4: journalctl 找不到日志#

Terminal window
# 原因:日志未持久化
# 检查存储模式
cat /etc/systemd/journald.conf | grep Storage
# 开启持久化
sudo mkdir -p /var/log/journal/
sudo systemctl restart systemd-journald
# 检查日志磁盘使用
journalctl --disk-usage

Q5: Type=forking 服务状态异常#

Terminal window
# forking 类型的服务需要 PIDFile
[Service]
Type=forking
PIDFile=/run/myapp.pid
ExecStart=/usr/bin/myapp --daemon
# 如果不设 PIDFile,systemd 可能找不到主进程
# 导致 status 显示 "failed" 但服务实际在运行

Q6: ExecStart 中的变量不展开#

Terminal window
# ❌ 错误:systemd 不做 shell 展开
ExecStart=/usr/bin/echo $HOME
# 实际执行:echo "$HOME"(字面量)
# ✅ 正确:用 Environment 设置
Environment=HOME=/root
ExecStart=/usr/bin/echo $HOME
# ✅ 需要 shell 特性时用 sh -c
ExecStart=/bin/sh -c 'echo $HOME'
ExecStart=/bin/sh -c 'cat /etc/config | grep key'
# ❌ 不支持的 shell 语法
ExecStart=/usr/bin/echo $(date) # 不工作
ExecStart=/usr/bin/echo `date` # 不工作
# ✅ 用 sh -c
ExecStart=/bin/sh -c 'echo $(date)'

Q7: 服务无法访问网络#

Terminal window
# 检查是否设置了网络隔离
systemctl show myapp | grep -i network
# 如果有 PrivateNetwork=true,改为 false
# 或检查 IPAddressDeny/Allow
# 确保依赖了 network-online.target
[Unit]
After=network-online.target
Wants=network-online.target
# 手动测试网络
sudo -u myappuser curl https://example.com

Q8: Timer 不触发#

Terminal window
# 检查 timer 是否启用
systemctl is-enabled myapp.timer
systemctl is-active myapp.timer
# 查看下次触发时间
systemctl list-timers myapp.timer
# 检查 timer 配置
systemctl cat myapp.timer
# 手动触发测试
systemctl start myapp.service # 直接启动关联的 service
# 查看 timer 日志
journalctl -u myapp.timer
journalctl -u myapp.service

Q9: Reload 不生效#

Terminal window
# 有些服务不支持 reload
systemctl reload myapp
# Failed to reload myapp.service: Job type reload is not applicable
# 检查是否定义了 ExecReload
systemctl show myapp -p ExecReload
# 如果为空,则不支持 reload
# 只能用 restart
systemctl restart myapp

Q10: 磁盘空间被日志占满#

Terminal window
# 检查日志大小
journalctl --disk-usage
# 立即清理
sudo journalctl --vacuum-size=100M
sudo journalctl --vacuum-time=3d
# 永久限制
sudo vim /etc/systemd/journald.conf
# SystemMaxUse=200M
sudo systemctl restart systemd-journald

十四、systemd 速查表#

14.1 systemctl 命令速查#

Terminal window
# 服务管理
systemctl start SERVICE # 启动
systemctl stop SERVICE # 停止
systemctl restart SERVICE # 重启
systemctl reload SERVICE # 重载配置
systemctl status SERVICE # 查看状态
systemctl enable SERVICE # 开机自启
systemctl disable SERVICE # 取消自启
systemctl enable --now SERVICE # 自启并启动
systemctl kill SERVICE # 杀进程
systemctl reset-failed SERVICE # 清除失败状态
# 查询
systemctl is-active SERVICE # 是否运行
systemctl is-enabled SERVICE # 是否自启
systemctl is-failed SERVICE # 是否失败
systemctl list-units # 已加载的 Unit
systemctl list-unit-files # 所有 Unit 文件
systemctl list-dependencies SERVICE # 依赖关系
systemctl cat SERVICE # 查看完整配置
systemctl show SERVICE # 查看所有属性
systemctl edit SERVICE # 编辑覆盖配置
systemctl list-timers # 查看所有 timer
# 系统
systemctl daemon-reload # 重新加载配置
systemctl reboot # 重启
systemctl poweroff # 关机
systemctl rescue # 救援模式
systemctl get-default # 默认启动目标
systemctl set-default TARGET # 设置默认目标

14.2 journalctl 命令速查#

Terminal window
journalctl -u SERVICE # 指定服务日志
journalctl -u SERVICE -f # 实时跟踪
journalctl -u SERVICE -n 50 # 最近 50 条
journalctl -u SERVICE -p err # 只看错误
journalctl -u SERVICE -r # 反向(最新在前)
journalctl -u SERVICE -o cat # 只输出消息
journalctl --since today # 今天
journalctl --since "1h ago" # 最近 1 小时
journalctl -b # 本次启动
journalctl -b -1 # 上次启动
journalctl -k # 内核日志
journalctl --disk-usage # 日志大小
journalctl --vacuum-size=100M # 清理到 100M
journalctl --vacuum-time=7d # 清理 7 天前

14.3 Service 文件速查#

[Unit]
Description= # 描述
After=network-online.target # 启动顺序
Wants=network-online.target # 弱依赖
Requires=postgresql.service # 强依赖
[Service]
Type=simple # 服务类型
User=appuser # 运行用户
WorkingDirectory=/opt/app # 工作目录
Environment=KEY=value # 环境变量
EnvironmentFile=/etc/app/env # 环境变量文件
ExecStart=/usr/bin/app # 启动命令
ExecStartPre=/usr/bin/init.sh # 启动前执行
ExecStop=/usr/bin/app stop # 停止命令
ExecReload=/usr/bin/app reload # 重载命令
Restart=always # 自动重启
RestartSec=5s # 重启间隔
TimeoutStartSec=30s # 启动超时
LimitNOFILE=65536 # 文件描述符
MemoryMax=512M # 内存限制
CPUQuota=100% # CPU 限制
[Install]
WantedBy=multi-user.target # 开机自启目标

14.4 Timer 语法速查#

OnCalendar=daily # 每天 00:00
OnCalendar=weekly # 每周一 00:00
OnCalendar=monthly # 每月 1 日 00:00
OnCalendar=*-*-* 02:00:00 # 每天 02:00
OnCalendar=*-*-* 02,14:00:00 # 每天 02:00 和 14:00
OnCalendar=Mon..Fri 09:00:00 # 工作日 09:00
OnBootSec=2min # 开机后 2 分钟
OnUnitActiveSec=1h # 上次执行后 1 小时
Persistent=true # 错过补执行
RandomizedDelaySec=5m # 随机延迟

十五、最佳实践#

15.1 服务部署 10 条原则#

  1. 用绝对路径 — ExecStart 必须是绝对路径,不用 ~$PATH./
  2. 指定 User — 不要用 root 跑服务,创建专用用户
  3. 设 Restart=always — 生产服务必须自动恢复
  4. 加 RestartSec — 避免崩溃循环重启拖垮系统
  5. daemon-reload — 改完 .service 文件必须 reload
  6. 用 systemctl edit — 不直接改原始 Unit 文件
  7. 加安全配置 — NoNewPrivileges + ProtectSystem + PrivateTmp
  8. 设资源限制 — MemoryMax + CPUQuota 防止资源耗尽
  9. 用 journalctl — 不在脚本里手动重定向日志
  10. 验证语法 — 部署前用 systemd-analyze verify 检查

15.2 场景推荐#

场景推荐方案关键配置
Web 服务Type=simple + Restart=alwaysExecStart=nginx -g 'daemon off;'
代理服务Type=simple + Restart=on-failureUser=nobody + CapabilityBoundingSet
一次性脚本Type=oneshot + RemainAfterExitExecStart=/opt/scripts/init.sh
定时任务.timer + .serviceOnCalendar=daily + Persistent=true
Docker 服务Type=oneshot + RemainAfterExitExecStart=docker compose up -d
数据库Type=forking + PIDFilePIDFile=/run/postgresql.pid
需要提权的服务AmbientCapabilitiesCAP_NET_BIND_SERVICE

15.3 生产级 Service 模板#

[Unit]
Description=Service Name
Documentation=https://example.com/docs
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=appuser
Group=appuser
WorkingDirectory=/opt/app
EnvironmentFile=/etc/app/env
ExecStart=/usr/bin/app --config /etc/app/config.yaml
Restart=always
RestartSec=5s
TimeoutStartSec=30s
TimeoutStopSec=15s
# 资源限制
MemoryMax=512M
CPUQuota=200%
LimitNOFILE=65536
# 安全加固
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/app /var/log/app
RestrictAddressFamilies=AF_INET AF_UNIX
RestrictNamespaces=true
LockPersonality=true
MemoryDenyWriteExecute=true
# 日志
StandardOutput=journal
StandardError=journal
SyslogIdentifier=app
[Install]
WantedBy=multi-user.target

总结#

systemd 是现代 Linux 的核心——从开机到关机、从服务到日志、从资源到安全,一切都由它管理。

核心知识回顾:

知识点一句话总结
systemctl启停服务、开机自启、状态查询的入口
Service 文件[Unit] + [Service] + [Install] 三段式配置
Typesimple 最常用,forking 对应传统守护进程
Restartalways 保证服务始终可用
journalctl统一日志查询,按服务/时间/级别过滤
Timer比 crontab 更强大的定时任务方案
资源控制MemoryMax + CPUQuota 限制服务资源
安全加固NoNewPrivileges + ProtectSystem + PrivateTmp
systemctl edit不改原文件的覆盖式修改
daemon-reload改完 .service 必须执行

三个关键规律:

  1. 服务即文件 — 每个 .service 文件就是一个服务的完整定义
  2. 日志统一收集 — 不用再各处找日志,journalctl 一把梭
  3. 资源可控可隔离 — cgroups 让每个服务都跑在沙箱里

掌握 systemd,你就掌握了现代 Linux 服务器管理的核心。从部署一个 Web 服务到管理整个服务器集群,systemd 都是最基础的工具。


推荐阅读#

systemd 服务管理完全指南:从 systemctl 到自定义服务与定时任务 | 2026 最新实践
https://971918.xyz/posts/docs/systemd-complete-guide/
作者
九所长
发布于
2026-07-08
许可协议
CC BY-NC-SA 4.0