nohup 和 setsid 启动 Node Nitro,为什么 SSH 一断服务就不行
最近排查一个很典型的问题:在服务器上通过 SSH 执行 nohup 或 setsid 启动 Node Nitro 服务,SSH 还开着的时候访问正常,一旦关闭 SSH,服务就不可用了。
这类问题第一反应很容易以为是 Nitro 或 Node 本身有问题,但多数时候真正原因在进程托管方式、登录会话清理、标准输入输出、监听地址或 SSH 端口转发上。
先判断是进程没了,还是进程还在但访问不到
不要一上来就改启动命令,先分清楚是哪一种情况。
SSH 断开前后分别执行:
“`bash
pgrep -af ‘node|nitro|nuxt’
ss -lntp | grep 3000
tail -n 100 nohup.out
“`
如果 SSH 断开后 pgrep 查不到 Node 进程,说明进程被登出会话带走了。
如果进程还在,但 ss 里只监听 127.0.0.1:3000,外部访问不到,那更可能是监听地址或反向代理问题。
如果之前是通过 SSH 端口转发访问,比如本地访问 localhost:3000 实际转发到远端,那么 SSH 一断端口转发也会断,这时服务本身可能还活着,只是访问路径没了。
nohup 不是服务管理器
nohup 的作用主要是忽略 SIGHUP,让进程不要因为终端挂起信号直接退出。它并不等于把程序交给系统长期托管。
如果服务器启用了 systemd-logind 的登录会话清理,或者进程仍然留在用户登录 session 的 cgroup 下面,SSH 退出时系统可能会把这批进程一起清掉。这个时候,只靠 nohup 不一定稳。
setsid 能让命令启动到新的 session 中,但如果标准输入、标准输出、标准错误还和 SSH 终端有关,或者外层 shell/job/cgroup 没处理干净,断开 SSH 后仍然可能出问题。
所以 nohup、setsid 更像临时保活手段,不应该作为生产服务的长期方案。
临时启动时要把输入输出彻底摘掉
如果只是临时跑一下,可以这样启动 Nitro:
“`bash
cd /path/to/your/app
nohup env NODE_ENV=production HOST=0.0.0.0 PORT=3000 node .output/server/index.mjs > app.log 2>&1 < /dev/null &
echo $! > app.pid
disown
“`
这里几个点都很关键:
> app.log 2>&1:标准输出和错误输出写入日志文件,不再写 SSH 终端。< /dev/null:标准输入不再占用 SSH。HOST=0.0.0.0:让服务监听所有网卡,不只监听本机回环地址。disown:从当前 shell 的 job 表里摘掉。
也可以用 setsid:
“`bash
setsid sh -c ‘cd /path/to/your/app && exec env NODE_ENV=production HOST=0.0.0.0 PORT=3000 node .output/server/index.mjs >> app.log 2>&1 < /dev/null’ &
“`
这比单纯 nohup node .output/server/index.mjs & 稳一些,但依然只是临时方案。
Nitro 要注意 HOST 配置
Nitro、Nuxt、Node 服务在不同环境下可能默认监听本机地址。如果只监听 127.0.0.1,外部机器当然访问不到。
生产环境通常需要明确指定:
“`bash
HOST=0.0.0.0
PORT=3000
NODE_ENV=production
“`
然后确认:
“`bash
ss -lntp | grep 3000
“`
如果看到 0.0.0.0:3000,说明对外网卡在监听。后面还要继续检查服务器防火墙、安全组、Nginx 反向代理是否放行。
如果看到 127.0.0.1:3000,说明它只允许本机访问。这种情况下,可以让 Nginx 在同机反代到 127.0.0.1:3000,也可以让应用直接监听 0.0.0.0,看你的部署策略。
长期运行应该交给 systemd
真正稳的方式是用 systemd 管理服务。比如创建:
“`ini
[Unit]
Description=Nitro App
After=network.target
[Service]
User=your-user
WorkingDirectory=/path/to/your/app
Environment=NODE_ENV=production
Environment=HOST=0.0.0.0
Environment=PORT=3000
ExecStart=/usr/bin/node /path/to/your/app/.output/server/index.mjs
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target
“`
保存为:
“`bash
/etc/systemd/system/nitro-app.service
“`
然后执行:
“`bash
sudo systemctl daemon-reload
sudo systemctl enable –now nitro-app
sudo systemctl status nitro-app
“`
查看日志:
“`bash
journalctl -u nitro-app -f
“`
这样服务就不依赖 SSH 会话了。SSH 断开、终端关闭、shell 退出,都不会影响服务运行。服务异常退出后,systemd 也可以按 Restart=always 自动拉起。
还要检查登录会话清理配置
如果你确认进程就是在 SSH 断开时被杀掉,可以看看:
“`bash
grep -E ‘KillUserProcesses|RemoveIPC’ /etc/systemd/logind.conf
loginctl show-user "$USER" | grep -E ‘Kill|Linger’
“`
某些系统或安全基线会启用用户进程清理。对长期服务来说,绕着这些配置打补丁不如直接使用 systemd 服务单元。
如果确实需要允许用户服务在退出登录后继续运行,也可以了解:
“`bash
loginctl enable-linger your-user
“`
不过对 Web 服务来说,我更推荐写成系统服务,边界清楚,也方便查看状态、日志和重启。
我的结论
遇到“SSH 开着服务正常,SSH 一断服务就不行”,排查顺序可以按这几步走:
- 先确认 SSH 断开后 Node 进程是否还存在。
- 再确认端口监听的是
127.0.0.1还是0.0.0.0。 - 再看是否依赖 SSH 端口转发。
- 临时运行时把 stdin/stdout/stderr 全部摘掉,并
disown。 - 长期运行直接上
systemd,不要把nohup当生产服务管理器。
一句话:nohup 和 setsid 可以救急,但不能替代服务管理。Node Nitro 这类 Web 服务,最终还是交给 systemd 或 PM2 这类进程管理器更稳。