Systemd 服务管理规范
2026/3/20大约 5 分钟
Systemd 服务管理规范
提示
现代 Linux 发行版都使用 systemd 管理服务。用好 systemd,让你的应用稳定运行、开机自启、崩溃自愈。
为什么用 Systemd?
| 特性 | 说明 |
|---|---|
| 进程守护 | 应用崩溃后自动重启 |
| 开机自启 | 服务器重启后自动拉起服务 |
| 日志集成 | 自动收集 stdout/stderr 到 journald |
| 依赖管理 | 定义服务启动顺序和依赖关系 |
| 资源限制 | 限制 CPU、内存、文件描述符等 |
服务文件位置
/etc/systemd/system/ # 自定义服务文件(推荐)
/lib/systemd/system/ # 系统/包管理器安装的服务
/usr/lib/systemd/system/ # 同上(某些发行版)
注意
永远将自定义服务放在 /etc/systemd/system/,不要修改 /lib/systemd/system/ 下的文件,系统更新会覆盖它们。
服务文件模板
基础模板
# /etc/systemd/system/app-blog.service
[Unit]
Description=Blog Application
Documentation=https://github.com/your/repo
After=network.target
[Service]
Type=simple
User=app-blog
Group=apps
WorkingDirectory=/opt/apps/blog/current
ExecStart=/usr/bin/node server.js
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
完整生产级模板
# /etc/systemd/system/app-blog.service
[Unit]
Description=Blog Application Server
Documentation=https://github.com/your/repo
# 依赖关系
After=network.target
After=postgresql.service
Wants=postgresql.service
[Service]
# 服务类型
Type=simple
# 运行用户
User=app-blog
Group=apps
# 工作目录
WorkingDirectory=/opt/apps/blog/current
# 环境变量
Environment=NODE_ENV=production
Environment=PORT=3000
EnvironmentFile=/opt/apps/blog/shared/.env
# 启动命令
ExecStart=/usr/bin/node server.js
# 重启策略
Restart=always
RestartSec=5
StartLimitInterval=60
StartLimitBurst=3
# 超时设置
TimeoutStartSec=30
TimeoutStopSec=30
# 安全加固
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict
ProtectHome=yes
ReadWritePaths=/opt/apps/blog/shared/uploads
ReadWritePaths=/var/log/apps/blog
# 资源限制
MemoryLimit=512M
CPUQuota=100%
LimitNOFILE=65535
# 日志
StandardOutput=journal
StandardError=journal
SyslogIdentifier=app-blog
[Install]
WantedBy=multi-user.target
配置项详解
[Unit] 部分
[Unit]
Description=服务描述,会显示在 systemctl status 中
Documentation=文档链接
# 启动顺序(在这些服务之后启动)
After=network.target postgresql.service redis.service
# 软依赖(希望这些服务一起启动,但不强制)
Wants=redis.service
# 硬依赖(这些服务必须存在且启动成功)
Requires=postgresql.service
# 冲突(这些服务不能同时运行)
Conflicts=nginx-old.service
[Service] 部分
服务类型
# simple(默认)- 主进程直接运行,适合大多数应用
Type=simple
ExecStart=/usr/bin/node server.js
# forking - 进程会 fork 到后台,需要配合 PIDFile
Type=forking
PIDFile=/var/run/app.pid
ExecStart=/opt/apps/blog/start.sh
# oneshot - 一次性任务,运行完就退出
Type=oneshot
ExecStart=/opt/apps/blog/migrate.sh
RemainAfterExit=yes
# notify - 进程会通知 systemd 已就绪(需要应用支持)
Type=notify
ExecStart=/usr/bin/python app.py
重启策略
# 重启条件
Restart=always # 总是重启(推荐用于服务)
Restart=on-failure # 仅在失败时重启
Restart=on-abnormal # 信号终止或超时时重启
Restart=no # 不重启
# 重启间隔
RestartSec=5 # 重启前等待 5 秒
# 防止无限重启
StartLimitInterval=60 # 60 秒内
StartLimitBurst=3 # 最多重启 3 次
# 超过则进入 failed 状态
环境变量
# 直接设置
Environment=NODE_ENV=production
Environment=PORT=3000
# 从文件加载(推荐)
EnvironmentFile=/opt/apps/blog/shared/.env
# .env 文件格式:
# NODE_ENV=production
# DATABASE_URL=postgres://...
# SECRET_KEY=xxx
安全加固选项
[Service]
# 禁止获取新特权
NoNewPrivileges=yes
# 使用私有 /tmp
PrivateTmp=yes
# 保护系统目录
ProtectSystem=strict # /usr, /boot, /etc 只读
ProtectHome=yes # 禁止访问 /home, /root
# 明确可写路径
ReadWritePaths=/opt/apps/blog/shared/uploads
ReadWritePaths=/var/log/apps/blog
# 限制网络
# PrivateNetwork=yes # 禁止网络(谨慎使用)
# 限制设备访问
PrivateDevices=yes
# 禁止加载内核模块
ProtectKernelModules=yes
资源限制
[Service]
# 内存限制
MemoryLimit=512M # 硬限制
MemoryHigh=400M # 软限制(超过会被限速)
# CPU 限制
CPUQuota=100% # 最多使用 1 个 CPU 核心
CPUQuota=200% # 最多使用 2 个核心
# 文件描述符
LimitNOFILE=65535
# 进程数
LimitNPROC=4096
# 核心转储大小
LimitCORE=infinity # 允许生成 core dump
常用操作命令
基本操作
# 重新加载服务文件(修改 .service 后必须执行)
sudo systemctl daemon-reload
# 启动/停止/重启
sudo systemctl start app-blog
sudo systemctl stop app-blog
sudo systemctl restart app-blog
# 重新加载配置(不重启进程,需要应用支持 SIGHUP)
sudo systemctl reload app-blog
# 查看状态
sudo systemctl status app-blog
# 设置开机自启
sudo systemctl enable app-blog
# 取消开机自启
sudo systemctl disable app-blog
# 启动并设置开机自启
sudo systemctl enable --now app-blog
日志查看
# 查看服务日志
journalctl -u app-blog
# 实时追踪
journalctl -u app-blog -f
# 最近 100 行
journalctl -u app-blog -n 100
# 今天的日志
journalctl -u app-blog --since today
# 指定时间范围
journalctl -u app-blog --since "2024-01-15 10:00" --until "2024-01-15 12:00"
# 只看错误
journalctl -u app-blog -p err
# 输出为 JSON
journalctl -u app-blog -o json
故障排查
# 查看服务详细信息
systemctl show app-blog
# 查看为什么启动失败
systemctl status app-blog
journalctl -xe -u app-blog
# 验证服务文件语法
systemd-analyze verify /etc/systemd/system/app-blog.service
# 查看服务依赖树
systemctl list-dependencies app-blog
# 查看启动耗时
systemd-analyze blame
实战示例
Node.js 应用
# /etc/systemd/system/app-api.service
[Unit]
Description=Node.js API Server
After=network.target
[Service]
Type=simple
User=app-api
Group=apps
WorkingDirectory=/opt/apps/api/current
Environment=NODE_ENV=production
EnvironmentFile=/opt/apps/api/shared/.env
ExecStart=/usr/bin/node dist/server.js
Restart=always
RestartSec=5
# Node.js 特定优化
Environment=UV_THREADPOOL_SIZE=16
LimitNOFILE=65535
[Install]
WantedBy=multi-user.target
Python (Gunicorn) 应用
# /etc/systemd/system/app-django.service
[Unit]
Description=Django Web Application
After=network.target
[Service]
Type=notify
User=app-django
Group=apps
WorkingDirectory=/opt/apps/django/current
EnvironmentFile=/opt/apps/django/shared/.env
ExecStart=/opt/apps/django/venv/bin/gunicorn \
--workers 4 \
--bind unix:/run/gunicorn/django.sock \
--access-logfile /var/log/apps/django/access.log \
--error-logfile /var/log/apps/django/error.log \
config.wsgi:application
ExecReload=/bin/kill -s HUP $MAINPID
Restart=on-failure
RestartSec=5
RuntimeDirectory=gunicorn
[Install]
WantedBy=multi-user.target
Java (Spring Boot) 应用
# /etc/systemd/system/app-spring.service
[Unit]
Description=Spring Boot Application
After=network.target
[Service]
Type=simple
User=app-spring
Group=apps
WorkingDirectory=/opt/apps/spring/current
EnvironmentFile=/opt/apps/spring/shared/.env
ExecStart=/usr/bin/java \
-Xms256m -Xmx512m \
-jar app.jar \
--spring.profiles.active=production
Restart=always
RestartSec=10
# Java 应用启动较慢
TimeoutStartSec=120
[Install]
WantedBy=multi-user.target
定时任务(替代 cron)
# /etc/systemd/system/backup-db.service
[Unit]
Description=Database Backup Job
[Service]
Type=oneshot
User=app-backup
ExecStart=/opt/scripts/backup-db.sh
# /etc/systemd/system/backup-db.timer
[Unit]
Description=Run database backup daily
[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
[Install]
WantedBy=timers.target
# 启用定时器
sudo systemctl enable --now backup-db.timer
# 查看定时器状态
systemctl list-timers
常见错误
❌ 错误做法
# 1. 用 nohup 或 & 后台运行
nohup node server.js & # 不可靠,无法管理
# 2. 用 root 运行服务
User=root # 危险!
# 3. 忘记 daemon-reload
# 修改 .service 文件后直接 restart,配置不生效
# 4. 硬编码环境变量
ExecStart=/usr/bin/node server.js --db-pass=123456 # 泄露风险
✅ 正确做法
# 1. 使用 systemd 管理
sudo systemctl start app-blog
# 2. 使用专用用户
User=app-blog
# 3. 修改后重载
sudo systemctl daemon-reload
sudo systemctl restart app-blog
# 4. 使用环境变量文件
EnvironmentFile=/opt/apps/blog/shared/.env