配好一台 Linux 服务器,装完软件只是第一步 —— 真正决定服务器长期健康运行的,是把服务管好:开机自启、异常自动拉起、日志可查、配置可改不重启。在主流发行版(Ubuntu、Debian、RHEL、Arch)上,这套管理工作几乎都由 systemd 接管。它既是 init 进程,又是服务管家,还是日志中心。本文从日常操作讲到手写 service 单元,全是能直接上手的实战内容。
systemd 是什么:三个核心概念
systemd 是如今 Linux 世界的标准初始化系统(PID 1),它把系统的每一个”进程单元”抽象成 unit(单元)。先记住这三个词,后面全都会用到:
- service:服务单元,后台常驻进程,如
nginx.service、sshd.service - target:目标的组合,相当于传统 SysV 的”运行级别”,如
multi-user.target(命令行)和graphical.target(图形界面) - journald:systemd 自带的日志守护进程,用
journalctl查看,统一收纳所有服务的标准输出和系统日志
你不需要知道每个发行版用哪个 systemd 版本也能照常使用本文所有命令。当前主流:Ubuntu 24.04 LTS 用 systemd 255,Debian 12 用 systemd 252,Ubuntu 22.04 用 249,命令语法完全兼容。
日常管理:systemctl 一页速查
管理服务就是 systemctl + 动作 + 服务名,.service 后缀可以省略:
systemctl start nginx # 启动
systemctl stop nginx # 停止
systemctl restart nginx # 重启
systemctl reload nginx # 重载配置(不中断进程,前提是服务支持)
systemctl status nginx # 状态 + 最近日志,排查问题第一步
systemctl enable nginx # 开机自启
systemctl disable nginx # 取消开机自启
systemctl enable --now nginx # 一键"开机自启 + 立即启动",最常用
检查状态的三种姿势
systemctl is-active nginx # active / inactive / failed —— 适合脚本判断
systemctl is-enabled nginx # enabled / disabled
systemctl is-failed nginx # active / failed
批量查看
systemctl list-units --type=service --state=running # 所有运行中的服务
systemctl list-units --type=service --state=failed # 挂掉的服务
systemctl --failed # 全部失败的 unit,开机后必查
systemctl list-timers --all # 定时器及其下次触发时间
systemctl list-unit-files --type=service # 所有已安装的 unit 文件
启动调优与系统控制
systemd-analyze # 开机总耗时
systemd-analyze blame # 每个服务分别耗时多少,找开机慢的元凶
systemd-analyze critical-chain # 启动关键路径,看卡在哪一环
systemctl reboot # 重启系统
systemctl poweroff # 关机
systemctl get-default # 当前默认 target(multi-user 或 graphical)
systemctl set-default multi-user.target # 命令行模式
避坑:
enable只设置自启不立即启动,start只启动不自启。生产环境部署新服务,请永远使用enable --now,别漏一半。
排查故障:从 status 到 journalctl
服务起不来是运维日常。标准的排查链是:
systemctl status myapp # 第一眼:状态、PID、退出的 exit code
journalctl -u myapp -xeu myapp # 日志全文:-x 带解释,-e 翻到末尾,-u 指定服务
常见 exit code 含义:
| 退出码 | 含义 | 常见原因 |
|---|---|---|
| 203 | Executable 不存在 | ExecStart 路径写错或没权限执行 |
| 200 | 加载 unit 失败 | 文件语法错误,先 daemon-reload |
| 216 | 用户/组不存在 | User=/Group= 指向了不存在的账户 |
修改了 unit 文件后必须执行 systemctl daemon-reload,否则 systemd 用的还是旧配置 —— 这是新手最常踩的坑。
systemctl daemon-reload
systemctl reset-failed myapp # 清除 failed 状态,反复重启失败的会触发 rate limit
实战:编写自己的 service 单元
业务脚本、爬虫、API 服务,不要再用 nohup ... & 裸跑 —— 退出后没人帮你拉起来,开机也不自启。手写一个 service 单元只需三步。
一个完整的示例
假设你有一个 Python 应用 /opt/myapp/app.py,创建 /etc/systemd/system/myapp.service:
[Unit]
Description=My Python Application
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=app
Group=app
WorkingDirectory=/opt/myapp
EnvironmentFile=/etc/myapp/env.conf
ExecStart=/usr/bin/python3 /opt/myapp/app.py
Restart=on-failure
RestartSec=3
LimitNOFILE=65535
[Install]
WantedBy=multi-user.target
然后一行启用:
sudo systemctl daemon-reload
sudo systemctl enable --now myapp
关键字段逐条讲
[Unit] 段:
Description:给人类看的说明,systemctl status时会显示After=:声明依赖顺序,等网络就绪再启动,防止启动即报”连不上网”
[Service] 段 —— 核心:
Type=simple:默认值,ExecStart 起的进程就是主进程,多数场景选它Type=forking:进程启动后会 fork 出子进程并自己退出(老派守护进程,如某些 Java 应用),需要配合PIDFile=User=/Group=:永远用专用用户跑服务,不要用 rootRestart=on-failure:崩溃/非正常退出时自动拉起;线上服务建议配合RestartSec=3防抖。always连正常退出也会拉起,慎用EnvironmentFile=:把敏感配置(密码、token)放独立文件,权限收紧到 600,改配置不用动 unitLimitNOFILE=:打开文件数上限,高并发服务必须调大,默认 1024 往往不够
[Install] 段:只有写了 WantedBy= 的 unit 才能被 enable,开机自启就是把自己挂到对应 target 下。
改配置不重启:drop-in 覆盖
官方推荐用 systemctl edit 加覆盖片段,不改动系统原文件,系统升级后配置也不会被冲掉:
sudo systemctl edit myapp
# 在弹出的编辑器中写入:
[Service]
RestartSec=5
保存后自动 daemon-reload,再 systemctl restart myapp 生效。查看合并后的最终配置:
systemctl cat myapp # 显示原文件 + 所有 drop-in 合并结果
统一日志:journalctl 实战
journald 把服务 stdout/stderr 和系统日志收进同一套日志,再也不用 cd /var/log 翻文件。
常用过滤
journalctl -u myapp # 只看某个服务的日志
journalctl -u myapp -f # 实时跟踪,等价于 tail -f,排查首选
journalctl -n 50 # 最近 50 条
journalctl -k # 内核日志
journalctl -b # 本次开机以来的日志
journalctl -b -1 # 上次开机(需持久化,见下文)
journalctl --list-boots # 列出所有开机记录
journalctl -p err # 只看 err 及以上级别
journalctl --since "1 hour ago" # 时间过滤:today / "30min ago" / 具体时间点
journalctl -u myapp --since today --until "10 minutes ago" # 组合过滤
journalctl --grep="OutOfMemory" # 按正则搜索内容
日志持久化:重启后日志不丢
默认日志存在 /run/log/journal(内存盘),重启即清空。想要跨重启保留,只需创建持久化目录,systemd 检测到后自动切换:
sudo mkdir -p /var/log/journal
sudo systemd-tmpfiles --create --prefix /var/log/journal
sudo systemctl restart systemd-journald
磁盘占用控制
日志跑久了会占磁盘,定期清理是基本功:
journalctl --disk-usage # 看占了多少
sudo journalctl --vacuum-size=500M # 清到 500MB 以下
sudo journalctl --vacuum-time=30d # 只保留最近 30 天
更进一步:在
/etc/systemd/journald.conf.d/下建00-storage.conf写[Journal]+Storage=persistent/SystemMaxUse=200M,一次性限制日志上限,比反复手工清理省心。
进阶:用 systemd timer 替代 cron
cron 的痛点是没有统一日志、错过时间不补跑。timer 在 systemd 生态里日志、依赖、失败追踪全都有。示例:每天 3 点跑备份脚本。
创建 /etc/systemd/system/backup.service:
[Unit]
Description=Nightly backup job
[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh
创建 /etc/systemd/system/backup.timer:
[Unit]
Description=Run backup daily at 3am
[Timer]
OnCalendar=daily
Persistent=true
[Install]
WantedBy=timers.target
启用方式与 service 相同:
sudo systemctl enable --now backup.timer
systemctl list-timers backup.timer # 查看下次触发时间
journalctl -u backup.service # 查看每次执行日志
Persistent=true 是杀手锏:机器当时关机没执行,下次开机自动补跑 —— 这是 cron 做不到的。OnCalendar= 语法与 cron 类似但更灵活,如 Mon..Fri *-*-* 09:00:00 表示工作日早上 9 点。
实战总结
- 部署服务:写 unit →
daemon-reload→enable --now→systemctl status确认 - 排查问题:
systemctl status看退出码 →journalctl -xeu看日志 - 服务异常:
Restart=on-failure自动拉起,别再用nohup裸跑 - 改配置:
systemctl edit加 drop-in,别直接动/usr/lib/systemd/ - 开机必查:
systemctl --failed,把红灯消灭在平时
从”能跑”到”跑得稳”,systemd 就是那道分水岭。
