VPS 上服务启动失败时,很多人只会反复执行:
sudo systemctl restart app
如果问题是配置没加载、路径写错、权限不足或端口被占用,重启一百次也没用。更糟的是,如果 Unit 配了 Restart=always,服务可能会进入重启循环,日志刷屏,CPU 和磁盘也被拖慢。
这篇专门讲 systemd 服务启动失败怎么查。它不是 Nginx、MySQL、Redis 某一个软件的专属排查,而是所有自建服务都会遇到的基础问题:systemctl status 怎么看、journalctl -u 看哪几行、Unit 文件怎么确认、什么时候需要 daemon-reload,以及 Start request repeated too quickly 怎么处理。
先看服务状态:
sudo systemctl status app --no-pager
把 app 换成你的服务名,比如:
sudo systemctl status nginx --no-pager
sudo systemctl status docker --no-pager
sudo systemctl status myapp --no-pager
重点看这些字段:
Loaded:Unit 文件是否存在、是否 enabled;Active:是active、failed、activating还是auto-restart;Main PID:主进程是否还在;ExecStart=后面的命令是否真的执行过;code=exited, status=...退出码是多少;- 最下面几行日志有没有明确报错。
如果只看到一小段日志,不够。下一步看完整 journal。
最常用命令:
sudo journalctl -u app -n 120 --no-pager
看最近 1 小时:
sudo journalctl -u app --since "1 hour ago" --no-pager
实时跟踪:
sudo journalctl -u app -f
如果刚刚启动失败,可以这样复现并立刻看日志:
sudo systemctl restart app
sudo journalctl -u app -n 120 --no-pager
不要只看最后一行。真正原因经常在前面,例如:
No such file or directory
Permission denied
Address already in use
Failed at step USER
Failed to load environment files
Working directory ... does not exist
Start request repeated too quickly
如果你还不熟悉 journalctl,可以先看这篇:VPS 日志怎么看(journalctl、systemctl、Nginx、应用日志)。
很多人改错文件。
先看 systemd 实际看到的 Unit 内容:
sudo systemctl cat app
它会显示主 Unit 文件和 drop-in 覆盖配置,例如:
# /etc/systemd/system/app.service
[Service]
WorkingDirectory=/var/www/app
ExecStart=/usr/bin/node server.js
# /etc/systemd/system/app.service.d/override.conf
[Service]
Environment=NODE_ENV=production
如果你明明改了 /etc/systemd/system/app.service,但 systemctl cat app 看不到变化,通常是忘了:
sudo systemctl daemon-reload
改 Unit 文件后推荐顺序:
sudo systemctl daemon-reload
sudo systemctl restart app
sudo systemctl status app --no-pager
只改应用配置文件,不一定需要 daemon-reload;改 .service、.timer、drop-in override,通常需要。
Unit 里的 ExecStart 最好写绝对路径:
[Service]
ExecStart=/usr/bin/node /var/www/app/server.js
不要依赖登录 shell 里的 PATH、nvm、pyenv 或 conda。systemd 启动服务时,环境和你 SSH 登录后不一样。
先查命令路径:
which node
which python3
which php
which gunicorn
如果你用 nvm 装 Node,which node 可能在用户家目录下:
/home/deploy/.nvm/versions/node/v20.11.1/bin/node
这类路径在 systemd 里也能用,但要确认运行用户有权限,并且不要假设 .bashrc 会被加载。更稳的做法是把运行环境固定到明确路径,或者写一个 wrapper 脚本。
很多应用手动启动没问题,systemd 启动失败,是因为工作目录不同。
常见写法:
[Service]
WorkingDirectory=/var/www/app
ExecStart=/usr/bin/npm start
检查目录:
ls -ld /var/www/app
如果日志里有:
CHDIR spawning ... No such file or directory
Working directory ... does not exist
说明 WorkingDirectory 路径错了,或者目录被删了。
如果目录存在,但服务用户进不去,也会失败。比如服务用 User=www-data,就要用这个用户测试:
sudo -u www-data test -x /var/www/app
sudo -u www-data ls /var/www/app
Unit 常见配置:
[Service]
User=deploy
Group=deploy
如果日志里出现:
Failed at step USER spawning ...: No such process
说明用户不存在或 systemd 无法切换到这个用户。
检查用户:
id deploy
如果是文件权限问题,日志可能是:
Permission denied
用目标用户手动执行启动命令:
sudo -u deploy /usr/bin/node /var/www/app/server.js
这比 root 手动执行更有参考价值。root 能跑,不代表 deploy、www-data、postgres、redis 能跑。
很多服务会这样写:
[Service]
EnvironmentFile=/etc/app/app.env
ExecStart=/usr/bin/node /var/www/app/server.js
如果文件不存在,日志可能出现:
Failed to load environment files: No such file or directory
检查:
sudo ls -l /etc/app/app.env
sudo systemctl cat app
如果这个环境文件不是必需的,可以加 - 让它不存在时不报硬错误:
EnvironmentFile=-/etc/app/app.env
但不要为了“能启动”就随便加 -。如果应用必须依赖数据库地址、密钥或端口配置,缺失环境文件只会让错误变得更隐蔽。
还要注意环境文件格式,不是完整 shell 脚本。常见写法:
NODE_ENV=production
PORT=3000
DATABASE_URL=postgres://user:[email protected]:5432/app
不要在里面写复杂 shell 逻辑。
如果日志里有:
Address already in use
EADDRINUSE
bind: address already in use
先查端口是谁占着:
sudo ss -lntup
只看某个端口:
sudo ss -lntup | grep ':3000'
常见原因:
- 旧进程没有退出;
- 同一个服务启动了两份;
- Docker 容器占用了端口;
- Nginx / Caddy / Apache 和应用抢 80/443;
- 配置里多个服务写了同一个端口。
不要直接 kill -9。先确认进程来源:
ps -fp PID
systemctl status PID对应服务
如果端口问题导致网站 502,可以看:VPS 出现 502 / 504 Bad Gateway 怎么办。
Unit 里常见:
[Unit]
After=network.target postgresql.service
Requires=postgresql.service
这只能保证顺序和依赖关系,不代表 PostgreSQL 已经能接受连接,也不代表网络上的远程数据库可用。
如果应用日志是:
connection refused
connection timed out
could not connect to server
要同时检查依赖服务:
sudo systemctl status postgresql --no-pager
sudo ss -lntup | grep ':5432'
如果是 Docker、Redis、MySQL、远程 API,也要分别确认可达性。不要把所有依赖问题都归咎于 systemd。
如果你看到:
Start request repeated too quickly
Failed with result 'start-limit-hit'
说明服务短时间失败太多次,被 systemd 暂停拉起。
先看失败原因:
sudo journalctl -u app -n 200 --no-pager
修复根因后,重置失败状态:
sudo systemctl reset-failed app
sudo systemctl start app
如果 Unit 里有:
[Service]
Restart=always
RestartSec=1
服务会非常频繁地重启。生产环境可以适当调大:
[Service]
Restart=on-failure
RestartSec=5s
不要用更激进的重启策略掩盖启动失败。它只会让日志更乱。
常见类型:
Type=simple
Type=exec
Type=forking
Type=oneshot
大多数前台运行的应用用 simple 或 exec 就够了。不要把一个前台进程写成 forking。
如果服务启动后立刻退出,但这是一次性任务,应该考虑:
[Service]
Type=oneshot
RemainAfterExit=yes
如果是长期运行服务,进程退出就说明应用本身结束了,要看应用日志,而不是把它伪装成成功。
建议每次改 systemd Unit 都按这个顺序:
sudo systemd-analyze verify /etc/systemd/system/app.service
sudo systemctl daemon-reload
sudo systemctl restart app
sudo systemctl status app --no-pager
sudo journalctl -u app -n 80 --no-pager
systemd-analyze verify 能提前发现一部分 Unit 语法和配置问题。它不能保证应用一定能启动,但能避免低级 Unit 错误。
如果线上服务重要,改之前先备份:
sudo cp /etc/systemd/system/app.service /etc/systemd/system/app.service.bak.$(date +%F-%H%M)
回滚时:
sudo cp /etc/systemd/system/app.service.bak.时间戳 /etc/systemd/system/app.service
sudo systemctl daemon-reload
sudo systemctl restart app
遇到 systemctl start 或 restart 失败,按这个顺序查:
systemctl status app --no-pager看 Active、退出码和最后日志。journalctl -u app -n 120 --no-pager看完整失败原因。systemctl cat app确认实际加载的 Unit 和 override。- 改过 Unit 后执行
systemctl daemon-reload。 - 检查
ExecStart是否为绝对路径,命令是否存在。 - 检查
WorkingDirectory是否存在,服务用户是否能访问。 - 检查
User/Group是否存在,权限是否正确。 - 检查
EnvironmentFile是否存在、格式是否正确。 - 用
ss -lntup检查端口是否被占用。 - 检查依赖服务是否启动并真正可连接。
- 遇到
start-limit-hit,先修根因,再systemctl reset-failed app。 - 修改前备份 Unit,修改后用
systemd-analyze verify做基础检查。
systemd 服务启动失败,不要靠反复 restart 碰运气。先用 systemctl status 判断状态,再用 journalctl -u 找第一条真正错误,接着看 systemctl cat 确认 Unit 是否按你想的加载。
大多数问题最后都会落到几类:路径不对、权限不够、环境文件缺失、端口冲突、依赖没起来、Unit 改了但没 daemon-reload,或者服务进入了 StartLimitHit。按顺序排查,比盲改配置快得多,也不会把现场越改越乱。
