发布时间:2026-09-10 04:33 更新时间:2026-09-10 04:33 阅读量:0
不少站长还习惯用 nohup 把常驻进程丢在后台,日志重定向到文件,靠 ps 和 kill 去管理。进程一旦崩了,不会自动拉起;服务器重启,还得手动登录再跑一遍。这个问题在 systemd 环境下其实早就有更规范的解法:把进程交给 systemd 托管,定义好 Unit 文件,就能实现开机自启、崩溃自动重启,还能用 systemctl 统一查看日志和状态。这篇教程就围绕 systemd 服务管理的核心操作展开,从写 Unit 文件到疑难排查,逐一过一遍。
systemd 把每个受管对象称为 Unit,服务类 Unit 以 .service 结尾。Unit 文件通常放在三个目录:/etc/systemd/system/ 是管理员自定义与覆盖的目录(优先级最高),/run/systemd/system/ 是运行时生成目录,/lib/systemd/system/ 是软件包随附目录。自己写的服务建议放到 /etc/systemd/system/ 下,避免被软件包更新覆盖。文件名即服务名,例如 nginx.service 对应服务 nginx。
一个最简单的 Unit 文件只有三个区块。以常驻的 Python 脚本示例,先创建 /etc/systemd/system/myapp.service,内容如下:
[Unit]
Description=My Python Background Service
After=network.target
[Service]
Type=simple
User=www
WorkingDirectory=/opt/myapp
ExecStart=/usr/bin/python3 /opt/myapp/main.py
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
[Unit] 里的 Description 是服务说明;After 声明在 network.target 之后启动,避免网络未就绪就拉起服务。[Service] 是核心部分:Type=simple 表示 ExecStart 启动的进程就是主进程,这是最常见的类型;User 指定运行用户,避免用 root 跑业务服务;ExecStart 写实际启动命令,路径要用绝对路径。这里特别建议用 which python3 确认解释器路径,不同发行版可能不一样。Restart=on-failure 是故障自动重启的关键策略,RestartSec=5 表示重启前等待 5 秒。[Install] 下的 WantedBy=multi-user.target 配合 enable 命令,让服务在系统进入多用户模式时启动。
选 Restart 策略时可参考这些取值:no 不自动重启(默认值);on-success 仅在进程正常退出时重启;on-failure 在退出码非零、被信号杀死或超时时重启,生产环境常用;always 无论何种原因退出都重启,适合需要持续在线的进程,但要注意如果进程因配置错误反复崩溃,会陷入重启循环。
写完 Unit 文件后,先让 systemd 重新加载配置,再启动服务。依次执行以下命令:
sudo systemctl daemon-reload
sudo systemctl enable myapp.service
sudo systemctl start myapp.service
sudo systemctl status myapp.service
enable 命令会在 /etc/systemd/system/multi-user.target.wants/ 目录下创建符号链接,实现开机自启。status 输出里能看到服务当前状态、主进程 PID、最近日志,如果 Active 不是 active (running),需要检查错误信息。日常运维常用命令还包括:systemctl restart myapp.service 重启服务;systemctl stop myapp.service 停止服务;systemctl disable myapp.service 取消开机自启,但不会停止当前运行的服务;systemctl is-enabled myapp.service 查看是否设置了开机自启。
查看服务日志不再需要自己拼 log 文件路径,systemd 会把 stdout 和 stderr 交给 journald。用 journalctl 即可定位:
sudo journalctl -u myapp.service -f
sudo journalctl -u myapp.service --since "10 minutes ago"
-f 表示持续跟踪新日志,类似 tail -f;--since 可以过滤时间段。如果要同时看服务启动以来的所有日志,直接 journalctl -u myapp.service 即可。这些日志默认存在 /var/log/journal/ 下,具体保留策略由系统配置决定。
新手在编写 Unit 文件时常踩几个坑。第一个坑是把 Type 设置错误。有些程序自己会在后台 fork 出子进程并退出父进程,这类程序如果仍用 Type=simple,systemd 会认为主进程已退出,进而按 Restart 策略反复拉起。对这类程序,应改用 Type=forking,并在 Unit 中指定 PIDFile,让 systemd 知道真正要监控的进程号。多数现代服务(如 nginx、sshd)的官方 Unit 文件都用了 forking 或 notify 类型,编写前先搞清楚目标进程是否存在守护化行为。
第二个坑是环境变量问题。systemd 服务默认不加载 .bashrc 或 /etc/profile 里的变量,如果程序依赖自定义 PATH 或特定环境变量,可以在 [Service] 区块用 Environment="KEY=value" 逐项声明,或使用 EnvironmentFile=/etc/myapp.env 导入整个文件。注意 EnvironmentFile 的路径需要预先存在,否则服务启动会失败。
第三个坑是重启循环。如果程序启动即崩溃,Restart=always 会触发无限快速重启,占用系统资源。可以用 StartLimitIntervalSec 和 StartLimitBurst 控制重启频率。例如在 [Unit] 区块加上:
StartLimitIntervalSec=60
StartLimitBurst=5
含义是 60 秒内最多重启 5 次,超过后 systemd 将不再尝试并标记服务失败。之后需要手动 systemctl reset-failed 清除失败状态,再重新启动。
还有一类常见需求:服务依赖数据库或网络。仅用 After=network.target 不一定够,因为 network.target 只代表网络设备就绪,不代表网络服务可用。更稳妥的方式是结合服务自身的健康检查脚本,或用 systemd 自带的 socket 激活机制。对大多数建站场景,在 Unit 里写 ExecStartPre 做预检查即可,比如等待某个 TCP 端口可连接再启动业务进程。
把常驻进程迁到 systemd 下,最直观的收益是进程生命周期不再依赖登录会话,服务器重启后服务自动恢复,崩溃后能按策略自动拉起,日志也能统一用 journalctl 检索。这套方法适用于各类后台服务,包括 Node.js 应用、Python 爬虫、私有脚本等。迁移时只需确认程序的启动方式、是否守护化,再对照本文写一个 Unit 文件测试即可。不同发行版的 systemd 版本和目录结构可能略有差异,具体可参考 systemd.service 与 systemd.unit 的手册页(man systemd.service)。掌握 systemctl 这一套命令,比维护一堆 nohup 脚本和 PID 文件要省心得多,也更能保证服务的稳定性。
| 📑 | 📅 |
|---|---|
| 网站服务器迁移完整流程:数据同步、解析切换与验证 | 2026-09-09 |
| 服务器日志分析:grep、awk 与 GoAccess 快速排障指南 | 2026-09-09 |
| Nginx反向代理配置详解:多站点代理与负载均衡实战 | 2026-09-09 |
| firewalld 防火墙实战指南:区域概念、端口放行与常用配置 | 2026-09-08 |
| Linux 服务器 SSH 安全加固实战:密钥认证、禁用 Root 与防暴力破解 | 2026-09-08 |
| Linux用户与权限管理实战:sudoers、SUID/SGID与最小权限 | 2026-09-10 |
| MySQL 出现 Too many connections 怎么办:定位、连接池与超时调优 | 2026-09-11 |
| Nginx 502/504 排查实战:从 upstream 超时到 php-fpm 进程池 | 2026-09-12 |
| Docker 磁盘占用过高清理实战:overlay2、容器日志与悬空镜像 | 2026-09-12 |
| Let's Encrypt 证书自动续期失败排查:日志、80端口校验与 reload 钩子 | 2026-09-13 |