PM2 + Nginx 部署 Node 服务前,先跑完这份检查

Node 服务上线不是 git pull && pm2 restart 就结束。容易出问题的是端口没监听、Nginx 还指旧端口、环境变量漏了、日志没落盘、回滚包找不到。

下面用一个 Express 服务举例,假设进程名叫 codenest,本机端口是 3000,公网由 Nginx 反代。

1. 上线前先确认工作区干净

git status --short
git rev-parse --short HEAD

如果不是 Git 部署,也要有等价版本号。没有版本号,出了问题很难说清楚线上跑的是哪份代码。

不要把 .env、pem、数据库文件、上传目录打进提交:

git status --short --ignored | head -80
git check-ignore -v .env || echo '.env is not ignored'

看到 .env is not ignored 就先停下来修 .gitignore

2. 本地先跑语法和启动检查

Node 没有统一编译步骤,至少可以做语法检查:

node --check server.js

如果项目拆了多个 JS 文件:

find services public -name '*.js' -print0 | xargs -0 -n1 node --check

注意:public 里的浏览器脚本如果用了 documentnode --check 只做语法解析,不会执行,不影响。

启动一次本地服务:

PORT=3000 npm start

另开终端验证:

curl -fsS http://127.0.0.1:3000/health

没有健康检查就补一个。上线时需要一个不依赖登录、不改数据、能快速返回的探针。

3. 上线前备份运行期数据

SQLite 示例:

stamp=$(date +%Y%m%d-%H%M%S)
mkdir -p data/backups
sqlite3 data/blog.db ".backup 'data/backups/blog-$stamp.sqlite'"
sqlite3 data/backups/blog-$stamp.sqlite 'pragma integrity_check;'

上传目录:

tar -czf data/backups/uploads-$stamp.tar.gz uploads
tar -tzf data/backups/uploads-$stamp.tar.gz >/dev/null

备份命令失败就停下。代码能回滚,用户上传文件丢了不一定能补回来。

4. 检查环境变量

只检查变量是否存在,不打印密钥值:

node - <<'NODE'
require('dotenv').config();
const required = [
  'APP_BASE_URL',
  'SESSION_SECRET',
  'GITHUB_CLIENT_ID',
  'GITHUB_CLIENT_SECRET',
  'GITHUB_CALLBACK_URL'
];
const missing = required.filter((name) => !process.env[name]);
if (missing.length) {
  console.error('missing env:', missing.join(', '));
  process.exit(1);
}
console.log('required env ok');
NODE

不要在日志里输出 SESSION_SECRET、OAuth secret、API key。排障时只输出“存在/不存在”和地址类非敏感字段。

5. PM2 重载前看状态

pm2 status
pm2 describe codenest
pm2 logs codenest --lines 40

重点看:

  • 进程是不是 online。
  • restart 次数有没有异常增长。
  • 错误日志里有没有已经存在的报错。

如果上线前日志已经在报错,先处理旧问题,不要把新版本叠上去。

6. Nginx 配置先做语法检查

sudo nginx -t

如果改过站点配置,再看反代端口:

sudo nginx -T 2>/dev/null | grep -n "proxy_pass\|server_name\|listen"

确认 proxy_pass 指向服务真实监听端口,比如:

proxy_pass http://127.0.0.1:3000;

Nginx 配置不通过时不要 reload。

7. 执行发布

常见顺序:

git pull --ff-only
npm ci --omit=dev
pm2 reload codenest --update-env

如果不是 Git 部署,把第一步换成你的解包或同步命令。关键是不要在同一次发布里混入不清楚来源的手工文件。

npm cinpm install 更适合部署,因为它按 lockfile 安装,依赖结果更可预期。

8. 发布后立刻验证本机和公网

本机:

curl -fsS http://127.0.0.1:3000/health

公网:

curl -I https://example.com/health
curl -I https://example.com/

example.com 换成你的域名。

看日志:

pm2 logs codenest --lines 80
tail -n 80 data/logs/app.log
tail -n 80 data/logs/error.log

检查点:

  • /health 本机和公网都返回 200。
  • 首页至少返回 200 或预期重定向。
  • PM2 restart 次数没有快速上涨。
  • error.log 没有新错误刷屏。

9. 回滚命令提前写好

回滚不要现场想。至少写清楚上一个版本和恢复数据的位置。

Git 回滚示例:

git log --oneline -5
git checkout <上一版commit>
npm ci --omit=dev
pm2 reload codenest --update-env
curl -fsS http://127.0.0.1:3000/health

如果这次发布包含数据库迁移,要提前写明:

  • 是否允许代码回滚但数据不回滚。
  • 是否有反向迁移脚本。
  • 哪些后台任务需要暂停。

没想清楚这些,不要把“可回滚”写进发布单。

10. 最小发布清单

每次上线前后把这段跑完:

git status --short
node --check server.js
sqlite3 data/blog.db ".backup 'data/backups/pre-release.sqlite'"
sqlite3 data/backups/pre-release.sqlite 'pragma integrity_check;'
sudo nginx -t
pm2 status
pm2 reload codenest --update-env
curl -fsS http://127.0.0.1:3000/health
tail -n 80 data/logs/error.log

如果其中任何一步失败,处理失败原因后再走下一步。发布流程最怕“上了再说”。