SSH 一断 Node 服务就挂?排查与解决方案记录

文章目录

引言

事情是这样的:我用 SSH 连上阿里云服务器,跑了一个简单的 Node.js 小项目。Express 起的 HTTP 服务,监听 3000 端口,代码在本地测得好好的,推到服务器上用 node app.js 一跑,也正常响应。但过了两天发现接口调不通了------登上服务器一看,进程没了。

重启,又过了一天,又没了。我开始觉得这不是偶然。

如果你也遇到过类似的情况------SSH 连着的时候一切正常,一旦断开终端或者网络闪断,服务就莫名其妙地挂掉------这篇文章就是写给你的。我们将从现象出发,一步步定位根因,理解 SIGHUP 信号的工作机制,最后给出四种解决方案,并重点展开 PM2 的用法,让你的 Node 服务真正"稳"在服务器上。

配置环境:Linux 全发行版(CentOS / Ubuntu / Debian 等),文中命令和配置在 Ubuntu 22.04 及 CentOS 7 上均可运行。Node.js 版本不限,以下示例基于 Node.js 18 LTS。

定位过程

第一步:排除代码问题

遇到服务挂掉,第一反应往往是代码有 bug。我把错误日志翻了一遍------什么都没有 。没有 uncaughtException,没有 process.on('error') 的输出,进程没有留下任何遗言就消失了。如果是代码抛异常,至少会有一个错误堆栈,但日志干净得像什么都没发生过。

这让我意识到:进程不是自己崩溃的,是被外部力量杀死的。

第二步:看进程还在不在

SSH 连上服务器后,用 ps 查看进程:

bash 复制代码
ps aux | grep node
# 空空如也------进程确实没了

正常退出的进程会留下退出码,系统日志里也可能有记录。但这里的情况是进程直接消失,说明它不是"主动退出",而是被终止信号干掉的。

第三步:复现------锁定触发条件

回想服务挂掉的时间点,都是在我合上笔记本盖、或者网络切换之后。那就主动复现一次:

bash 复制代码
# 终端 A:SSH 登录服务器,启动服务
ssh user@my-server
node app.js
# 输出:Server running on port 3000

# 然后直接关掉终端 A

# 终端 B:另一台机器立刻测试
curl http://my-server:3000/
# 结果:Connection refused ------ 服务已经挂了

复现成功。 只要 SSH 断开,进程立刻消失。触发条件锁定了。

第四步:理解信号

在 Linux 中,每个进程都从属于一个会话(Session)和一个进程组(Process Group) 。当你通过 SSH 登录时,你会得到一个终端(TTY),所有在这个终端里启动的进程都属于同一个会话。当你关闭终端或 SSH 断连时,内核会向这个会话中的所有进程发送一个信号------SIGHUP(Hangup,挂断信号)

node app.js 直接在前台运行,它是 Shell 会话的子进程。会话结束 → 收到 SIGHUP → 默认行为退出。这就是根因。

根因分析

SIGHUP 是什么

SIGHUP(Signal Hangup)是 Unix/Linux 系统中最古老的信号之一,编号为 1。它最初的设计目的是在调制解调器(modem)连接的电话线路挂断时通知进程。虽然现在没人再用拨号上网了,但这个信号被保留了下来,并且被赋予了新的职责:在终端关闭时通知该终端下的所有进程

当一个终端(TTY)关闭时,内核会遍历这个终端上的所有进程,向它们发送 SIGHUP。进程收到 SIGHUP 后的默认行为立即终止 。你的 node app.js 没有显式捕获这个信号,那就走默认行为------退出。

为什么不是 Node.js 的问题

有人可能会怀疑是 Node.js 的事件循环或异步处理导致的问题。实际上,这和 Node 一点关系都没有。可以试试其他环境,比如python:

bash 复制代码
# 开一个终端 SSH 进去,运行
python3 -m http.server 3000

# 断开 SSH,服务同样会挂

# 换成 Go 写的二进制也一样
./my-go-server

# 断开 SSH,一样挂

任何程序直接在终端前台运行,都会因为终端关闭而被 SIGHUP 杀掉。 这是 Linux 进程模型决定的,不挑语言。

📌 核心结论: Node 进程和 SSH 会话是父子关系。父进程(Shell)死的时候,子进程(Node)会收到 SIGHUP 信号,默认行为就是退出。你把 SSH 终端一关,等于亲手给 Node 进程发了一张"死亡通知单"。

解决方案

知道原因就好办了。解决思路很明确:让进程脱离 SSH 会话独立运行,并且最好能挂了自动重启。

下面按推荐程度从低到高介绍四种方案。

方案一:nohup------最简单的一行命令

nohup(no hangup)的作用就是让进程忽略 SIGHUP 信号:

bash 复制代码
nohup node app.js > app.log 2>&1 &

各参数含义:

部分 含义
nohup 让进程忽略 SIGHUP 信号,终端关了也不退出
> app.log 将标准输出重定向到 app.log 文件
2>&1 将标准错误也合并到标准输出,一起写入 app.log
& 将进程放到后台运行,不占用终端

运行后会输出一个进程 ID(PID),可以这样查看:

bash 复制代码
# 查看进程是否在运行
ps -p [PID]

# 查看日志
tail -f app.log

优点 :一条命令搞定,所有 Linux 发行版自带,无需安装任何东西。

缺点:进程崩溃了不会自动重启;重启服务器后需要手动再跑一遍;管理多个进程时完全靠脑子记。适合"临时跑一会儿看看效果",不适合长期运行。

方案二:screen / tmux------虚拟终端保活

screentmux 创建了一个独立于 SSH 会话的虚拟终端。即使你的 SSH 连接断了,这个虚拟终端还在运行,里面的进程也安然无恙。

tmux 为例(Ubuntu 下 sudo apt install tmux 安装):

bash 复制代码
# 创建一个名为 myapp 的会话
tmux new -s myapp

# 在 tmux 窗口里启动服务
node app.js

# 按 Ctrl+B,再按 D ------ 从 tmux 会话中"分离"(detach),回到普通终端

# 现在可以安全退出 SSH 了,服务继续跑

# 下次 SSH 登录后,重新连接 tmux 会话
tmux attach -t myapp
# 又看到了熟悉的 Node 输出,就像从未离开过

screen 的用法类似:

bash 复制代码
screen -S myapp        # 创建会话
node app.js            # 启动服务
# 按 Ctrl+A,再按 D ------ 分离

screen -r myapp        # 重新连接
screen -ls             # 查看所有会话

优点 :可以随时 attach 回去看实时输出,方便调试;同时管理多个窗口。

缺点:依旧不会自动重启崩溃的进程;服务器重启后需要手动重建会话。

适合开发调试阶段,想随时看到程序输出了什么。

方案三:PM2------Node.js 生产环境首选 ⭐

PM2(Process Manager 2)是 Node.js 生态中最成熟的进程管理器,也是本文的核心推荐方案。它同时解决了进程守护、日志管理、自动重启、开机自启等多个生产环境的刚需问题。详细内容可参考【PM2 教程】从零开始的 PM2 进程管理实战:让你的 Node.js 应用永不掉线

安装与基础使用
bash 复制代码
# 全局安装
npm install -g pm2

# 启动应用并命名为 my-app(--name 便于后续管理)
pm2 start app.js --name my-app

参数说明:

  • start:启动一个应用
  • app.js:入口文件路径
  • --name my-app:给应用起一个名字,后续操作靠这个名字来定位,比记 PID 方便得多

运行后会输出一个进程表格,类似这样:

plain 复制代码
┌─────┬──────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐
│ id  │ name     │ namespace   │ version │ mode    │ pid      │ uptime │ ↺    │ status    │ cpu      │ mem      │ user     │ watching │
├─────┼──────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤
│ 0   │ my-app   │ default     │ 1.0.0   │ fork    │ 12345    │ 0s     │ 0    │ online    │ 0%       │ 25.3mb   │ user     │ disabled │
└─────┴──────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘
PM2 核心能力拆解

① 进程守护与自动重启

PM2 启动的应用是 PM2 守护进程(daemon)的子进程。SSH 断开时,PM2 守护进程本身不受影响(它以 daemon 方式运行,不依附于任何终端),所以它管理的 Node 进程也不会收到 SIGHUP。

更关键的是,如果 Node 进程因为代码异常而崩溃,PM2 会自动重启它。默认情况下,PM2 会立即重启,如果在短时间内多次崩溃,它会指数退避(exponential backoff),避免死循环式的重启风暴。

② 日志管理

不用再手动重定向日志了:

bash 复制代码
# 实时查看日志(所有应用)
pm2 logs

# 只看 my-app 的日志
pm2 logs my-app

# 清空日志
pm2 flush

PM2 自动收集 stdout 和 stderr,存储在 ~/.pm2/logs/ 目录下,按应用名称分文件,而且会自动处理日志轮转。

③ 开机自启

服务器重启后,PM2 可以自动恢复之前运行的所有应用:

bash 复制代码
# 生成开机启动脚本(根据你的 init 系统自动选择 systemd/upstart 等)
pm2 startup

# 保存当前进程列表,下次开机自动恢复
pm2 save

pm2 startup 的输出会告诉你接下来需要执行什么命令(通常是一条 sudo 命令来注册 systemd 服务),照着做就行。

④ 零停机重载(Graceful Reload)

代码更新后要重启服务,直接 kill 进程会导致正在处理的请求中断。PM2 提供了热重载:

bash 复制代码
# 零停机重载(逐个 worker 重启,始终有进程在服务)
pm2 reload my-app

# 注意:pm2 restart 会先停再启,有短暂的服务中断
# 生产环境应该用 reload 而非 restart

⚠️ 风险提示pm2 restart 会先停止进程再启动,造成短暂的服务不可用。在生产环境中重启服务时,请使用 pm2 reload 来实现零停机。另外,pm2 save 会保存当前进程快照------如果某次调试时临时起了一个进程并执行了 pm2 save,这个临时进程也会在下次重启时被恢复。记得定期检查 pm2 list 确认只保存了需要持久化的应用。

常用命令速查
命令 作用
pm2 list 查看所有应用状态
pm2 logs [name] 查看日志
pm2 restart [name] 重启应用(有中断)
pm2 reload [name] 零停机重载
pm2 stop [name] 停止应用
pm2 delete [name] 从 PM2 中删除应用
pm2 monit 终端内的实时监控面板
pm2 save 保存当前进程列表
pm2 startup 生成开机自启脚本
pm2 info [name] 查看单个应用的详细信息

方案四:systemd------Linux 原生方案

如果你不想在服务器上装 Node 生态的工具,或者需要管理的不只是 Node 应用,systemd 是 Linux 系统自带的服务管理器,同样可以实现进程守护和开机自启。

创建服务配置文件:

bash 复制代码
sudo vim /etc/systemd/system/my-app.service

写入以下内容:

properties 复制代码
[Unit]
Description=My Node.js App
After=network.target

[Service]
Type=simple
User=www
WorkingDirectory=/opt/my-app
ExecStart=/usr/bin/node /opt/my-app/app.js
Restart=always
RestartSec=5
Environment=NODE_ENV=production
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target

关键参数解释:

参数 含义
After=network.target 在网络就绪之后再启动服务,避免启动时网络不可达
Type=simple 最简单的启动类型,systemd 认为 ExecStart 启动的进程就是主服务进程
User=www www 用户身份运行(不要用 root,降低安全风险)
WorkingDirectory 进程的工作目录,确保相对路径引用正确
ExecStart 启动命令,必须使用绝对路径which node 可以查到 Node 的安装路径
Restart=always 无论什么原因退出都自动重启------这就是进程守护的核心
RestartSec=5 重启前等待 5 秒,避免崩溃后立刻重启陷入死循环
Environment 设置环境变量,相当于 export NODE_ENV=production

部署与验证:

bash 复制代码
# 重新加载 systemd 配置
sudo systemctl daemon-reload

# 设为开机自启
sudo systemctl enable my-app

# 启动服务
sudo systemctl start my-app

# 查看状态
sudo systemctl status my-app

# 查看日志
sudo journalctl -u my-app -f

优点 :系统自带、零依赖、统一管理各种语言的服务。

缺点 :配置比 PM2 稍微繁琐;日志用 journalctl 查看,没有 PM2 的 pm2 logs 那么直观。

方案对比与选择建议

方案 自动重启 开机自启 日志管理 上手难度 适用场景
nohup 临时跑一下,几分钟的事
screen/tmux ⭐⭐ 开发调试,随时想看输出
PM2 ⭐⭐ Node 生产环境首选(推荐)
systemd ⭐⭐⭐ 不想装额外依赖、多语言混合管理

决策建议

  • 只是在服务器上临时测试一下 → nohup,一行命令的事
  • 开发阶段,想随时 attach 回去看 console.logtmux
  • 你的 Node 项目要正式部署了PM2,十分钟配置好,省下未来无数次排查"服务怎么又挂了"的时间
  • 团队有运维规范要求用 systemd 统一管理所有服务 → systemd

如果你和我一样了解过前端/Node,我建议直接上 PM2。它专为 Node 设计,配置文件简单,常用命令不超过 10 条,而且自带 pm2 monit 监控面板非常直观。

总结

回顾整个排查过程,我们从一个看似"随机"的服务挂掉现象出发,通过逐步复现锁定了 SSH 断连这个触发条件,找到了幕后真凶 SIGHUP 信号,最终理解了 Linux 终端会话与进程组的关系。

这个问题的本质教训是:别在 SSH 终端里裸跑服务。 任何需要长期运行的后台服务,都应该通过进程管理器来守护------无论是 PM2、systemd 还是 Docker,它们的核心能力之一就是帮你的进程扛住 SIGHUP,并且在崩溃时自动拉起来。

本文重点推荐的 PM2,用三条命令就能让服务稳稳地跑起来:

bash 复制代码
npm install -g pm2
pm2 start app.js --name my-app
pm2 save && pm2 startup

从此 SSH 随便断,网络随便闪,你的 Node 服务都不会莫名其妙地消失了。

适用边界说明 :本文讨论的场景是 Linux 服务器上通过 SSH 直接运行 Node 应用的情况。如果你使用的是 Docker 容器部署,容器内的进程管理逻辑不同(PID 1 问题),应优先通过 Docker 的 restart policy 来保障服务可用性。Windows Server 上的 IISNode 或 Windows Service 部署也不在本文讨论范围。


参考

相关推荐
武器大师721 小时前
实战踩坑:Gerrit HTTP 克隆失败解决方案
运维·nginx·gerrit
kaka❷❷1 小时前
Linux 内核、.ko、.so 与 SDK 镜像打包
linux·运维·服务器
微风◝2 小时前
【Linux故障排查】系统启动进入紧急模式:由磁盘挂载超时引发的服务器无法启动
linux·运维·服务器
闲猫2 小时前
堡垒机Linux黑屏识别命令Set -n探索可能性
linux·运维·服务器
寺中人2 小时前
基于Linux实现SSH密钥免密登录完整实战教程(CentOS/Ubuntu通用)
linux·ssh·免密登录·服务器运维·ssh-keygen
lilihuigz2 小时前
从“拥有AEO工具”到“拥有AEO代理”:三层架构解决营销自动化瓶颈 - 易服客工作室
运维·自动化
万粉变现经纪人2 小时前
2026最新Windows11系统CMD安装Claude Code 快速接入DeepSeek V4 Pro在VSCode编程工具中使用保姆级入门教程指南
linux·运维·ide·windows·vscode·macos·编辑器
jike88ai2 小时前
Windows版Claude Code安装与API对接教程(附常见问题解决)
windows·gpt·node.js·claude·claudecode·88api
木雷坞2 小时前
自托管 n8n:Docker Compose、Webhook 和升级备份排查
运维·容器