nodemon重启Node服务报EADDRINUSE端口占用的长效解决方案
长效修复方案
核心根因
反复触发Error: listen EADDRINUSE: address already in use的本质不是外部程序占用端口,而是nodemon执行重启逻辑时,旧的Node服务进程未收到终止信号、或因代码逻辑阻塞无法正常退出,成为孤儿进程持续绑定监听端口。更换端口后依然报错的原因是:新端口启动的进程在下次重启时同样无法正常退出,依然会出现新旧进程端口冲突。
分步修复
1. 修正nodemon终止信号配置(90%的场景可直接解决)
nodemon默认向子进程发送SIGUSR2信号触发重启,部分Node版本/框架对该信号的默认处理逻辑有缺陷,无法自动终止服务释放端口。在项目根目录新建nodemon.json配置文件,替换为通用的终止信号并增加重启延迟,避免端口释放的时序问题:
{ "signal": "SIGINT", "delay": 800, "watch": ["src/"], "ext": "js,ts,json", "exec": "node src/index.js" }
配置说明:
SIGINT和手动执行Ctrl+C终止进程的信号一致,Node原生HTTP服务、Express/Koa等框架都能正确响应该信号,自动停止端口监听- 800ms延迟用于等待旧进程完全释放端口句柄,避免新进程启动时端口仍处于TIME_WAIT状态
2. 代码层增加优雅退出逻辑
不要直接调用app.listen()不接收返回值,需手动保存服务实例,监听所有进程终止信号,主动关闭服务释放端口,从代码层面避免端口残留:
const express = require('express') const app = express() const PORT = 8000 // 保存server实例,用于后续主动关闭 const server = app.listen(PORT, () => { console.log(`服务已启动,监听端口${PORT}`) }) // 优雅退出逻辑 const shutdown = (signalType) => { console.log(`收到终止信号${signalType},开始释放资源`) server.close(() => { console.log('端口已释放,进程正常退出') process.exit(0) }) // 兜底超时:5秒后强制退出,避免逻辑卡死导致进程残留 setTimeout(() => process.exit(1), 5000) } // 监听所有常见终止信号 process.on('SIGINT', shutdown) process.on('SIGTERM', shutdown) process.on('SIGUSR2', shutdown)
3. 排查阻塞进程退出的残留逻辑
如果上述配置完成后依然有进程残留,说明代码中存在未清理的常驻任务,导致进程收到终止信号后无法自动退出:
- 检查是否有未清除的
setInterval/setTimeout常驻定时器 - 检查数据库、Redis、MQ等长连接是否在退出时主动调用了
end()/close()方法 - 检查是否有fork的子进程未正常销毁
可以通过以下命令启动服务,追踪进程无法退出的具体原因:
nodemon --trace-exit src/index.js
4. 兜底自动清理配置(可选,适配特殊环境)
如果是Windows环境(系统信号机制与类Unix系统不兼容),可以在nodemon.json中增加重启前的自动清理逻辑,每次重启前自动杀掉占用端口的残留进程:
{ "events": { "restart": "lsof -ti tcp:8000 | xargs kill -9" } }
Windows环境替换restart命令为:
taskkill /F /IM node.exe
验证方式
配置完成后连续修改3-5次代码触发nodemon自动重启,执行lsof -i tcp:8000查看监听进程,若始终只有1个node进程存在,说明修复生效,不会再出现EADDRINUSE报错。
注意:不建议长期靠手动杀进程、换端口解决问题,这类方案没有解决旧进程残留的核心问题,开发过程中会反复触发报错。
内容的提问来源于stack exchange,提问作者Romanric Akam
相关产品推荐
相关产品推荐

