systemd 服务管理完全指南:从 systemctl 到自定义服务与定时任务 | 2026 最新实践
你有没有遇到过这些场景:
- VPS 上跑的代理服务突然挂了,人不在电脑前,服务也不会自动重启
- 写了个 Python 脚本想后台运行,用
nohup跑起来后忘了 PID,找不回来也杀不掉 - Nginx 改了配置,不确定
systemctl reload和systemctl restart有什么区别 - 服务器日志散落在 /var/log/ 各个目录,找一个报错翻半天
- crontab 定时任务不执行,但不知道哪里看错误日志
systemd 就是解决这些问题的核心工具——它是现代 Linux 的「大管家」,管理着从开机到关机、从网络到日志的一切。
如果你在用 Linux 服务器(Ubuntu 16.04+、CentOS 7+、Debian 8+),你已经在用 systemd 了。每次执行 systemctl start nginx、systemctl 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
| 特性 | SysVinit | systemd |
|---|---|---|
| 启动方式 | 串行启动 | 并行启动 |
| 依赖管理 | 手动编号 S01, S02 | 自动 After/Requires |
| 服务管理 | service nginx start | systemctl start nginx |
| 配置文件 | /etc/init.d/ 脚本 | /etc/systemd/system/ .service |
| 日志 | 各服务各自管理 | journalctl 统一管理 |
| 定时任务 | crontab | systemd timer |
| 资源控制 | 不支持 | cgroups 原生支持 |
| 服务监控 | 不监控 | 自动重启崩溃服务 |
| 状态查询 | 不支持 | systemctl status 实时状态 |
1.3 核心组件
systemd├── systemd (PID 1) # 主进程,系统初始化├── systemctl # 服务管理命令├── journalctl # 日志查看命令├── journald # 日志收集守护进程├── networkd # 网络管理├── resolved # DNS 解析├── timesyncd # 时间同步├── logind # 用户会话管理└── cgroups # 资源控制1.4 验证 systemd
# 检查 systemd 是否是 PID 1ps -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 服务生命周期管理
# === 启动 / 停止 / 重启 ===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.service2.2 查看服务状态
# === 基础状态 ===systemctl status nginx # 详细状态(含最近日志)systemctl is-active nginx # 是否运行中 → active / inactivesystemctl is-enabled nginx # 是否开机自启 → enabled / disabledsystemctl is-failed nginx # 是否失败 → failed / success
# === 批量查看 ===systemctl list-units --type=service # 所有已加载的 servicesystemctl 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 # 主进程 PIDsystemctl show nginx -p ActiveState # 激活状态systemctl show nginx -p MemoryCurrent # 当前内存使用systemctl show nginx -p CPUUsageNSec # CPU 使用时间2.3 status 输出详解
$ 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逐行解读:
| 行 | 含义 |
|---|---|
Loaded | Unit 文件位置;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 系统级操作
# === 系统电源管理 ===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 ApplicationDocumentation=https://example.com/docsAfter=network-online.targetWants=network-online.targetRequires=postgresql.serviceConflicts=old-app.service
[Service]Type=simpleUser=appuserGroup=appuserWorkingDirectory=/opt/myappEnvironment=NODE_ENV=productionEnvironment=PORT=3000EnvironmentFile=-/etc/myapp/envExecStart=/usr/bin/node /opt/myapp/server.jsExecStartPre=/usr/bin/mkdir -p /var/run/myappExecStartPost=/usr/bin/sh -c 'echo "App started" > /var/log/myapp-start.log'ExecStop=/usr/bin/kill -TERM $MAINPIDExecReload=/usr/bin/kill -HUP $MAINPIDRestart=alwaysRestartSec=5sTimeoutStartSec=30sTimeoutStopSec=15sRuntimeMaxSec=7dLimitNOFILE=65536LimitNPROC=4096
# 资源限制MemoryMax=512MCPUQuota=100%IOWeight=500
# 安全加固NoNewPrivileges=truePrivateTmp=trueProtectSystem=strictProtectHome=trueReadWritePaths=/var/lib/myapp /var/log/myappCapabilityBoundingSet=CAP_NET_BIND_SERVICEAmbientCapabilities=CAP_NET_BIND_SERVICE
[Install]WantedBy=multi-user.target3.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.targetWants=network-online.target3.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=simpleExecStart=/usr/bin/python3 /opt/app/server.py
# Type=forking:传统守护进程(如 sshd)[Service]Type=forkingPIDFile=/run/sshd.pidExecStart=/usr/sbin/sshd
# Type=oneshot:一次性任务[Service]Type=oneshotExecStart=/opt/app/init.shRemainAfterExit=yes # 脚本执行完仍标记为 active
# Type=notify:服务自己通知就绪[Service]Type=notifyExecStart=/usr/sbin/mysqldNotifyAccess=mainExec* 指令
# ExecStart:启动命令(必须用绝对路径)ExecStart=/usr/bin/nginx -g 'daemon off;'
# 多行 ExecStart(Type=oneshot 时按顺序执行)ExecStart=/usr/bin/mkdir -p /var/run/myappExecStart=/usr/bin/chown appuser:appuser /var/run/myapp
# ExecStartPre:启动前执行ExecStartPre=/usr/bin/rm -f /var/run/myapp.pidExecStartPre=/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 reloadRestart(自动重启)
# 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=productionEnvironment=PORT=3000Environment="API_KEY=sk-xxx" # 含特殊字符用引号
# 从文件读取EnvironmentFile=/etc/myapp/env # 文件内容:KEY=VALUEEnvironmentFile=-/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 0 | poweroff.target | 关机 |
| runlevel 1 | rescue.target | 单用户/救援模式 |
| runlevel 2 | multi-user.target | 多用户(无网络) |
| runlevel 3 | multi-user.target | 多用户(有网络,命令行) |
| runlevel 4 | — | 未使用 |
| runlevel 5 | graphical.target | 图形界面 |
| runlevel 6 | reboot.target | 重启 |
4.2 Target 操作
# 查看当前默认目标systemctl get-default# 输出: multi-user.target
# 设置默认目标systemctl set-default graphical.targetsystemctl set-default multi-user.target
# 查看目标包含的服务systemctl list-dependencies multi-user.target
# 临时切换目标systemctl isolate rescue.target # 进入救援模式systemctl isolate emergency.target # 进入紧急模式
# 查看所有 targetsystemctl list-units --type=targetsystemctl list-unit-files --type=target4.3 常用 Target
# 网络相关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.target和network-online.target不同。network.target在网络栈初始化时就激活,但此时网络可能还没通。需要网络真正可用的服务用After=network-online.target+Wants=network-online.target。
五、journalctl 日志管理
5.1 基础查询
# === 按服务查看 ===journalctl -u nginx # 查看 nginx 全部日志journalctl -u nginx -u php-fpm # 同时查看多个服务journalctl -u nginx.service # 完整名称
# === 实时跟踪 ===journalctl -u nginx -f # 类似 tail -fjournalctl -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日志优先级:
| 数字 | 关键字 | 含义 |
|---|---|---|
| 0 | emerg | 紧急:系统不可用 |
| 1 | alert | 警报:必须立即处理 |
| 2 | crit | 严重:严重错误 |
| 3 | err | 错误 |
| 4 | warning | 警告 |
| 5 | notice | 通知 |
| 6 | info | 信息 |
| 7 | debug | 调试 |
5.2 高级查询
# === 按可执行文件过滤 ===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 # 美化 JSONjournalctl -u nginx -o verbose # 详细字段journalctl -u nginx --no-hostname # 不显示主机名
# === 反向输出(最新在上) ===journalctl -u nginx -r # 反向(最新在前)journalctl -u nginx -rn 20 # 反向最近 20 条5.3 日志维护
# === 查看日志大小 ===journalctl --disk-usage# Archived and active journals take up 500M in the file system.
# === 清理日志 ===# 按大小清理journalctl --vacuum-size=100M # 只保留最近 100Mjournalctl --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 # 日志最大 500MSystemMaxFileSize=50M # 单个文件最大 50MMaxRetentionSec=1month # 保留 1 个月RateLimitIntervalSec=30s # 限速:30 秒内RateLimitBurst=10000 # 最多 10000 条# 修改后重载sudo systemctl restart systemd-journald5.4 日志实战
# === 排查服务启动失败 ===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" # 内核日志中搜索 OOMjournalctl --since today -p err | grep -i oom
# === 导出日志给他人分析 ===journalctl -u nginx --since "1 hour ago" --no-pager > nginx-log.txtjournalctl -u nginx -o json-pretty > nginx-log.json六、编写自定义服务
6.1 Python Web 服务
sudo vim /etc/systemd/system/myapp.service[Unit]Description=My Python Web ApplicationDocumentation=https://github.com/myorg/myappAfter=network-online.targetWants=network-online.target
[Service]Type=simpleUser=appuserGroup=appuserWorkingDirectory=/opt/myappEnvironment="PYTHONUNBUFFERED=1"Environment="FLASK_ENV=production"EnvironmentFile=/etc/myapp/envExecStart=/opt/myapp/venv/bin/python /opt/myapp/app.pyRestart=alwaysRestartSec=5s
# 资源限制MemoryMax=512MCPUQuota=200%
# 安全加固NoNewPrivileges=truePrivateTmp=trueProtectSystem=strictReadWritePaths=/opt/myapp/data /var/log/myapp
# 日志StandardOutput=journalStandardError=journalSyslogIdentifier=myapp
[Install]WantedBy=multi-user.target# 创建用户和目录sudo useradd -r -s /bin/false appusersudo mkdir -p /opt/myapp/data /var/log/myappsudo chown -R appuser:appuser /opt/myapp /var/log/myapp
# 部署服务sudo systemctl daemon-reloadsudo systemctl enable --now myappsudo systemctl status myapp
# 查看日志journalctl -u myapp -f6.2 Node.js 服务
[Unit]Description=Node.js API ServerAfter=network-online.targetWants=network-online.target
[Service]Type=simpleUser=nodeappGroup=nodeappWorkingDirectory=/opt/api-serverEnvironment=NODE_ENV=productionEnvironment=PORT=3000ExecStart=/usr/bin/node /opt/api-server/dist/index.jsRestart=alwaysRestartSec=3s
# 优雅停止KillSignal=SIGINTTimeoutStopSec=10s
# 文件描述符限制LimitNOFILE=65536
# 安全加固NoNewPrivileges=truePrivateTmp=trueProtectSystem=strictProtectHome=trueReadWritePaths=/opt/api-server/logs /opt/api-server/uploads
[Install]WantedBy=multi-user.target6.3 Docker Compose 服务
[Unit]Description=Docker Compose Application StackRequires=docker.serviceAfter=docker.service
[Service]Type=oneshotRemainAfterExit=yesWorkingDirectory=/opt/myappExecStart=/usr/bin/docker compose up -dExecStop=/usr/bin/docker compose downExecReload=/usr/bin/docker compose restartTimeoutStartSec=0
[Install]WantedBy=multi-user.target# 使用sudo systemctl start myapp-docker # docker compose up -dsudo systemctl stop myapp-docker # docker compose downsudo systemctl reload myapp-docker # docker compose restart6.4 代理服务(V2Ray/Xray)
[Unit]Description=Xray ServiceDocumentation=https://xtls.github.ioAfter=network-online.target nss-lookup.targetWants=network-online.target
[Service]Type=simpleUser=nobodyCapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICEAmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICENoNewPrivileges=trueExecStart=/usr/local/bin/xray run -config /usr/local/etc/xray/config.jsonRestart=on-failureRestartPreventExitStatus=23LimitNPROC=10000LimitNOFILE=1000000
[Install]WantedBy=multi-user.target6.5 Shell 脚本服务
[Unit]Description=Log Rotation Custom ScriptAfter=network-online.target
[Service]Type=oneshotExecStart=/opt/scripts/log-rotate.shRemainAfterExit=yes
# 超时保护TimeoutStartSec=300
[Install]WantedBy=multi-user.target6.6 服务部署检查清单
# 1. 验证 Unit 文件语法systemd-analyze verify /etc/systemd/system/myapp.service
# 2. 重新加载systemctl daemon-reload
# 3. 启动并查看状态systemctl start myappsystemctl 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 # 等待 RestartSecsystemctl status myapp # 应该自动恢复为 active七、systemctl edit 修改系统服务
7.1 覆盖目录
systemctl edit 创建覆盖文件,不修改原始 Unit 文件:
# 编辑服务的覆盖配置sudo systemctl edit nginx
# 这会创建:# /etc/systemd/system/nginx.service.d/override.conf
# 编辑器中写入覆盖内容:[Service]Restart=alwaysRestartSec=3sLimitNOFILE=65536
# 保存后自动 reloadsudo systemctl daemon-reloadsudo systemctl restart nginx7.2 常见覆盖场景
# 场景1:给 Nginx 增加文件描述符限制sudo systemctl edit nginx[Service]LimitNOFILE=65536# 场景2:让 Docker 不自动重启sudo systemctl edit docker[Service]Restart=no# 场景3:修改 SSH 端口(不推荐改 service 文件,改 sshd_config 更好)sudo systemctl edit sshd[Service]ExecStart=ExecStart=/usr/sbin/sshd -D -p 2222注意: 清空
ExecStart=必须先写一个空值,再写新值。因为ExecStart是单值指令,需要先清除原值。
# 场景4:给服务添加环境变量sudo systemctl edit myapp[Service]Environment="DEBUG=true"Environment="LOG_LEVEL=debug"7.3 查看完整配置(含覆盖)
# 查看合并后的完整配置systemctl cat nginx
# 查看实际生效的属性systemctl show nginx -p Restartsystemctl show nginx -p LimitNOFILE7.4 撤销覆盖
# 撤销 overridesudo systemctl revert nginx
# 或手动删除sudo rm -rf /etc/systemd/system/nginx.service.d/sudo systemctl daemon-reloadsudo systemctl restart nginx八、systemd Timer 定时任务
8.1 Timer vs Crontab
| 特性 | crontab | systemd timer |
|---|---|---|
| 最小精度 | 1 分钟 | 1 秒甚至毫秒 |
| 依赖管理 | 无 | After/Wants/Requires |
| 日志 | 手动重定向 | 自动 journalctl |
| 错过执行 | 不补执行 | Persistent=true 可补执行 |
| 资源限制 | 不支持 | cgroups 限制 |
| 随机延迟 | 不支持(需 sleep) | RandomizedDelaySec |
| 条件触发 | 不支持 | ConditionPathExists 等 |
| 配置复杂度 | 简单 | 较复杂(两个文件) |
8.2 Timer 文件结构
Timer 需要两个文件:一个 .timer 定义何时触发,一个 .service 定义执行什么。
[Unit]Description=Daily Backup Timer
[Timer]OnCalendar=daily # 每天触发Persistent=true # 错过了补执行RandomizedDelaySec=5m # 随机延迟 0-5 分钟
[Install]WantedBy=timers.target[Unit]Description=Daily Backup Service
[Service]Type=oneshotExecStart=/opt/scripts/backup.shUser=backup# 启用sudo systemctl daemon-reloadsudo systemctl enable --now backup.timer
# 查看 timer 状态systemctl list-timerssystemctl list-timers --allsystemctl status backup.timer8.3 触发时间配置
[Timer]# === 日历时间 ===OnCalendar=daily # 每天 00:00OnCalendar=weekly # 每周一 00:00OnCalendar=monthly # 每月 1 日 00:00OnCalendar=*-*-* 02:00:00 # 每天 02:00OnCalendar=*-*-* 02,14:00:00 # 每天 02:00 和 14:00OnCalendar=Mon *-*-* 03:00:00 # 每周一 03:00OnCalendar=*-*-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 示例
# === 示例1:每天凌晨 3 点备份 ===[Unit]Description=Daily Backup at 3 AM
[Timer]OnCalendar=*-*-* 03:00:00Persistent=trueRandomizedDelaySec=5m
[Install]WantedBy=timers.target
# === 示例2:每 30 分钟同步 ===# /etc/systemd/system/sync.timer[Unit]Description=Sync Every 30 Minutes
[Timer]OnBootSec=5minOnUnitActiveSec=30minAccuracySec=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:00Persistent=true
[Install]WantedBy=timers.target
# === 示例4:每 10 秒检查一次 ===# /etc/systemd/system/health-check.timer[Unit]Description=Health Check Every 10 Seconds
[Timer]OnBootSec=10sOnUnitActiveSec=10sAccuracySec=1s
[Install]WantedBy=timers.target8.5 从 crontab 迁移到 Timer
# crontab 条目:# 0 3 * * * /opt/scripts/backup.sh
# 迁移步骤:
# 1. 创建 service 文件cat > /etc/systemd/system/backup.service << 'EOF'[Unit]Description=Backup Script
[Service]Type=oneshotExecStart=/opt/scripts/backup.shUser=backupEOF
# 2. 创建 timer 文件cat > /etc/systemd/system/backup.timer << 'EOF'[Unit]Description=Daily Backup
[Timer]OnCalendar=*-*-* 03:00:00Persistent=true
[Install]WantedBy=timers.targetEOF
# 3. 启用systemctl daemon-reloadsystemctl enable --now backup.timer8.6 查看 Timer 状态
# 查看所有 timersystemctl list-timerssystemctl 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
# 查看特定 timersystemctl status backup.timersystemctl list-timers backup.timer
# 查看 timer 触发历史journalctl -u backup.timerjournalctl -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=fifoCPUSchedulingPriority=509.2 内存限制
[Service]# 内存上限(硬限制,超过会被 OOM Kill)MemoryMax=512MMemoryMax=2G
# 内存软限制(超过会回收,但不杀进程)MemoryHigh=400M
# 内存+Swap 总限制MemorySwapMax=256M
# OOM 行为OOMPolicy=stop # OOM 时停止服务(默认)OOMPolicy=continue # OOM 时杀掉子进程但服务继续OOMPolicy=kill # OOM 时杀掉整个 cgroup9.3 IO 限制
[Service]# IO 权重(1-10000,默认 100)IOWeight=500
# 读写速率限制IOReadBandwidthMax=/dev/sda 10M # /dev/sda 上读速 10MB/sIOWriteBandwidthMax=/dev/sda 10M # /dev/sda 上写速 10MB/s
# IOPS 限制IOReadIOPSMax=/dev/sda 1000 # 1000 IOPSIOWriteIOPSMax=/dev/sda 1000
# 设备访问限制DeviceAllow=/dev/null rw # 只允许访问 /dev/nullDevicePolicy=closed # 禁止访问未列出的设备9.4 文件描述符与进程数
[Service]# 文件描述符限制LimitNOFILE=65536 # 最大打开文件数LimitNPROC=4096 # 最大进程数
# 其他 ulimitLimitCORE=infinity # core 文件大小LimitFSIZE=infinity # 文件大小LimitCPU=infinity # CPU 时间LimitAS=infinity # 虚拟内存LimitRSS=infinity # 常驻内存9.5 查看资源使用
# 查看服务资源使用systemctl show nginx -p MemoryCurrent -p CPUUsageNSec -p TasksCurrent
# 使用 systemd-cgtop 实时监控systemd-cgtop# 类似 top,但按 cgroup 分组
# 查看控制组systemd-cglssystemd-cgls nginx.service
# 查看资源限制配置systemctl show nginx -p MemoryMax -p CPUQuota -p LimitNOFILE十、安全加固
10.1 权限最小化
[Service]# 禁止获取新权限NoNewPrivileges=true
# 临时文件私有化(/tmp 隔离)PrivateTmp=true
# 禁止访问 /homeProtectHome=true
# 禁止访问 /rootProtectHome=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 # 禁止修改系统时钟
# hostnameProtectHostname=true # 禁止修改 hostname10.2 Capability 限制
[Service]# 丢弃所有 capabilities,只保留必要的CapabilityBoundingSet=# 只允许绑定 1024 以下端口CapabilityBoundingSet=CAP_NET_BIND_SERVICEAmbientCapabilities=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=myappGroup=myappNoNewPrivileges=true
# === 文件系统隔离 ===ProtectSystem=strictProtectHome=truePrivateTmp=trueReadWritePaths=/var/lib/myapp /var/log/myapp
# === 设备与内核隔离 ===PrivateDevices=trueProtectKernelModules=trueProtectKernelLogs=trueProtectControlGroups=trueProtectClock=trueProtectHostname=true
# === 网络限制 ===RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
# === Namespace 限制 ===RestrictNamespaces=trueRestrictRealtime=trueRestrictSUIDSGID=trueLockPersonality=true
# === 内存保护 ===MemoryDenyWriteExecute=true
# === 系统调用过滤 ===SystemCallFilter=@system-serviceSystemCallFilter=~@privileged @resources
# === Capability ===CapabilityBoundingSet=AmbientCapabilities=
# === 日志 ===StandardOutput=journalStandardError=journal10.5 安全审计
# 使用 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 创建 socket2. 服务持续运行 2. 服务不启动(不占资源)3. 请求到达 → 服务处理 3. 请求到达 → systemd 启动服务 → 转交 socket4. 空闲时也运行 4. 空闲时不运行11.2 Socket 文件
[Unit]Description=My App Socket
[Socket]ListenStream=3000 # 监听 TCP 3000 端口# ListenStream=/run/myapp.sock # 或监听 Unix SocketAccept=no # no=单进程处理所有连接, yes=每连接一进程
[Install]WantedBy=sockets.target# 注意:ExecStart 不需要自己监听端口# 服务通过 systemd 传入的 socket fd 接收连接[Service]Type=simpleExecStart=/usr/bin/myapp# 启用sudo systemctl enable --now myapp.socket# 服务会在第一次连接时自动启动11.3 Docker Socket 激活
# Docker 默认使用 socket 激活sudo systemctl enable docker.socketsudo systemctl start docker.socket
# 只有执行 docker 命令时,dockerd 才真正启动# 空闲时不占内存十二、systemd-analyze 调试工具
12.1 启动分析
# 总启动时间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 security12.2 配置验证
# 验证 Unit 文件语法systemd-analyze verify /etc/systemd/system/myapp.service
# 验证所有 Unit 文件systemd-analyze verify
# 分析 Unit 依赖systemd-analyze dot nginx.service | dot -Tsvg > /tmp/deps.svg12.3 日志级别
# 设置 systemd 调试日志级别sudo systemctl log-level debug# ... 操作 ...sudo systemctl log-level info # 恢复
# 查看调试日志journalctl -u systemd -p debug --since "5 min ago"十三、常见问题排查
Q1: 服务启动失败
# 步骤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 :PORTQ2: 改了 .service 文件不生效
# 必须重新加载sudo systemctl daemon-reload
# 然后重启服务sudo systemctl restart myapp
# 如果还是不生效,检查是否有 overridesystemctl cat myappQ3: 服务不停重启
# 查看重启次数和限制systemctl show myapp -p NRestarts -p StartLimitBurst -p StartLimitIntervalUSec
# 如果达到限制,服务会变为 failed# 重置失败状态sudo systemctl reset-failed myappsudo systemctl start myapp
# 调整重启限制[Service]StartLimitBurst=10StartLimitIntervalSec=60RestartSec=10sQ4: journalctl 找不到日志
# 原因:日志未持久化# 检查存储模式cat /etc/systemd/journald.conf | grep Storage
# 开启持久化sudo mkdir -p /var/log/journal/sudo systemctl restart systemd-journald
# 检查日志磁盘使用journalctl --disk-usageQ5: Type=forking 服务状态异常
# forking 类型的服务需要 PIDFile[Service]Type=forkingPIDFile=/run/myapp.pidExecStart=/usr/bin/myapp --daemon
# 如果不设 PIDFile,systemd 可能找不到主进程# 导致 status 显示 "failed" 但服务实际在运行Q6: ExecStart 中的变量不展开
# ❌ 错误:systemd 不做 shell 展开ExecStart=/usr/bin/echo $HOME# 实际执行:echo "$HOME"(字面量)
# ✅ 正确:用 Environment 设置Environment=HOME=/rootExecStart=/usr/bin/echo $HOME
# ✅ 需要 shell 特性时用 sh -cExecStart=/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 -cExecStart=/bin/sh -c 'echo $(date)'Q7: 服务无法访问网络
# 检查是否设置了网络隔离systemctl show myapp | grep -i network
# 如果有 PrivateNetwork=true,改为 false# 或检查 IPAddressDeny/Allow
# 确保依赖了 network-online.target[Unit]After=network-online.targetWants=network-online.target
# 手动测试网络sudo -u myappuser curl https://example.comQ8: Timer 不触发
# 检查 timer 是否启用systemctl is-enabled myapp.timersystemctl is-active myapp.timer
# 查看下次触发时间systemctl list-timers myapp.timer
# 检查 timer 配置systemctl cat myapp.timer
# 手动触发测试systemctl start myapp.service # 直接启动关联的 service
# 查看 timer 日志journalctl -u myapp.timerjournalctl -u myapp.serviceQ9: Reload 不生效
# 有些服务不支持 reloadsystemctl reload myapp# Failed to reload myapp.service: Job type reload is not applicable
# 检查是否定义了 ExecReloadsystemctl show myapp -p ExecReload# 如果为空,则不支持 reload
# 只能用 restartsystemctl restart myappQ10: 磁盘空间被日志占满
# 检查日志大小journalctl --disk-usage
# 立即清理sudo journalctl --vacuum-size=100Msudo journalctl --vacuum-time=3d
# 永久限制sudo vim /etc/systemd/journald.conf# SystemMaxUse=200Msudo systemctl restart systemd-journald十四、systemd 速查表
14.1 systemctl 命令速查
# 服务管理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 # 已加载的 Unitsystemctl 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 命令速查
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 # 清理到 100Mjournalctl --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:00OnCalendar=weekly # 每周一 00:00OnCalendar=monthly # 每月 1 日 00:00OnCalendar=*-*-* 02:00:00 # 每天 02:00OnCalendar=*-*-* 02,14:00:00 # 每天 02:00 和 14:00OnCalendar=Mon..Fri 09:00:00 # 工作日 09:00OnBootSec=2min # 开机后 2 分钟OnUnitActiveSec=1h # 上次执行后 1 小时Persistent=true # 错过补执行RandomizedDelaySec=5m # 随机延迟十五、最佳实践
15.1 服务部署 10 条原则
- 用绝对路径 — ExecStart 必须是绝对路径,不用
~、$PATH、./ - 指定 User — 不要用 root 跑服务,创建专用用户
- 设 Restart=always — 生产服务必须自动恢复
- 加 RestartSec — 避免崩溃循环重启拖垮系统
- daemon-reload — 改完 .service 文件必须 reload
- 用 systemctl edit — 不直接改原始 Unit 文件
- 加安全配置 — NoNewPrivileges + ProtectSystem + PrivateTmp
- 设资源限制 — MemoryMax + CPUQuota 防止资源耗尽
- 用 journalctl — 不在脚本里手动重定向日志
- 验证语法 — 部署前用
systemd-analyze verify检查
15.2 场景推荐
| 场景 | 推荐方案 | 关键配置 |
|---|---|---|
| Web 服务 | Type=simple + Restart=always | ExecStart=nginx -g 'daemon off;' |
| 代理服务 | Type=simple + Restart=on-failure | User=nobody + CapabilityBoundingSet |
| 一次性脚本 | Type=oneshot + RemainAfterExit | ExecStart=/opt/scripts/init.sh |
| 定时任务 | .timer + .service | OnCalendar=daily + Persistent=true |
| Docker 服务 | Type=oneshot + RemainAfterExit | ExecStart=docker compose up -d |
| 数据库 | Type=forking + PIDFile | PIDFile=/run/postgresql.pid |
| 需要提权的服务 | AmbientCapabilities | CAP_NET_BIND_SERVICE |
15.3 生产级 Service 模板
[Unit]Description=Service NameDocumentation=https://example.com/docsAfter=network-online.targetWants=network-online.target
[Service]Type=simpleUser=appuserGroup=appuserWorkingDirectory=/opt/appEnvironmentFile=/etc/app/envExecStart=/usr/bin/app --config /etc/app/config.yamlRestart=alwaysRestartSec=5sTimeoutStartSec=30sTimeoutStopSec=15s
# 资源限制MemoryMax=512MCPUQuota=200%LimitNOFILE=65536
# 安全加固NoNewPrivileges=truePrivateTmp=trueProtectSystem=strictProtectHome=trueReadWritePaths=/var/lib/app /var/log/appRestrictAddressFamilies=AF_INET AF_UNIXRestrictNamespaces=trueLockPersonality=trueMemoryDenyWriteExecute=true
# 日志StandardOutput=journalStandardError=journalSyslogIdentifier=app
[Install]WantedBy=multi-user.target总结
systemd 是现代 Linux 的核心——从开机到关机、从服务到日志、从资源到安全,一切都由它管理。
核心知识回顾:
| 知识点 | 一句话总结 |
|---|---|
| systemctl | 启停服务、开机自启、状态查询的入口 |
| Service 文件 | [Unit] + [Service] + [Install] 三段式配置 |
| Type | simple 最常用,forking 对应传统守护进程 |
| Restart | always 保证服务始终可用 |
| journalctl | 统一日志查询,按服务/时间/级别过滤 |
| Timer | 比 crontab 更强大的定时任务方案 |
| 资源控制 | MemoryMax + CPUQuota 限制服务资源 |
| 安全加固 | NoNewPrivileges + ProtectSystem + PrivateTmp |
| systemctl edit | 不改原文件的覆盖式修改 |
| daemon-reload | 改完 .service 必须执行 |
三个关键规律:
- 服务即文件 — 每个 .service 文件就是一个服务的完整定义
- 日志统一收集 — 不用再各处找日志,journalctl 一把梭
- 资源可控可隔离 — cgroups 让每个服务都跑在沙箱里
掌握 systemd,你就掌握了现代 Linux 服务器管理的核心。从部署一个 Web 服务到管理整个服务器集群,systemd 都是最基础的工具。
推荐阅读
- Linux 常用命令速查手册:100+ 命令 — systemctl 命令基础
- Shell 脚本编程完全指南 — 编写要作为服务运行的脚本
- SSH 完全指南:从远程连接到安全加固 — 远程管理 systemd 服务
- Nginx 反向代理配置实战 — Nginx 就是 systemd 管理的服务
- Tmux 终端复用器完全指南 — 长时间运行的服务管理会话