上一篇 下一篇 分享链接 返回 返回顶部

Node.js网站部署到香港服务器后如何稳定运行:Nginx、PM2与日志轮转怎么配

发布人:Minchunlin 发布时间:18小时前 阅读量:20
Node.js网站部署到香港服务器后如何稳定运行:Nginx、PM2与日志轮转怎么配

先把稳定运行的边界划清

典型现场是:Node.js项目在香港服务器上已经能通过 node server.js 启动,浏览器也能打开页面,但一旦SSH会话断开,网站就停止响应;改完配置后Nginx出现502;几天后日志占满磁盘,证书到期却没有及时加载。问题往往不在Node.js本身,而在入口、进程、日志和资源边界没有形成闭环。

一个适合生产环境的基本组合是:公网只暴露Nginx使用的80和443端口,Node.js只监听 127.0.0.1;PM2由systemd拉起并负责进程重启;Nginx和PM2日志分别轮转;证书续期后自动重新加载Nginx;发布前保留上一版本和关键配置,出现故障时能够切回。下面以使用systemd的Ubuntu或Debian类系统为例,假设应用由非root用户 deploy 运行,域名为 example.com,Node.js应用监听3000端口。

部署前先确认运行条件

检查系统、用户和Node.js环境

生产环境不要用root直接运行Node.js。先确认服务器上的用户、Node.js、npm、Nginx和PM2路径:

id deploy
node --version
npm --version
command -v node
command -v npm
command -v pm2
nginx -v
systemctl is-system-running

如果Node.js或PM2是通过nvm安装的,需要特别记录 command -v node 和 command -v pm2 的输出。systemd服务不会完全复用交互式Shell中的nvm环境,后续必须确认PM2生成的服务能够找到正确的Node.js路径。

创建应用目录时只授予部署用户权限,避免应用进程拥有不必要的系统写权限:

sudo install -d -o deploy -g deploy -m 0755 /srv/node-site
sudo install -d -o deploy -g deploy -m 0755 /srv/node-site/releases

应用代码、构建产物和依赖应使用项目的锁定文件安装。以npm项目为例:

sudo -iu deploy
cd /srv/node-site/current

npm ci
npm run build

如果项目构建后只需要生产依赖,可以在确认构建已经完成后执行:

npm prune --omit=dev

这会修改当前目录的 node_modules,不适合在没有锁定文件或没有回滚版本的情况下盲目执行。更稳妥的方式是把每次发布放入独立的 releases 目录,再用 current 符号链接指向当前版本。

确认应用的监听地址和健康检查接口

应用不应直接监听公网地址。常见写法如下:

const port = Number(process.env.PORT || 3000);
const host = process.env.HOST || '127.0.0.1';

server.listen(port, host, () => {
  console.log(`server listening on ${host}:${port}`);
});

如果应用使用Express、Koa或其他框架,还应准备一个不依赖复杂业务的健康检查接口,例如 /healthz。它至少应能反映Node.js进程和HTTP服务是否正常,不建议把大量数据库查询或第三方接口调用放进最基础的存活检查。

启动前先确认3000端口没有被其他程序占用:

ss -lntp | grep ':3000' || true

生产部署需要的最低前置条件包括:

  • 域名已经指向这台香港服务器,且站点使用的域名与Nginx的 server_name 一致。
  • 应用能够在本机通过 127.0.0.1:3000 访问。
  • 部署用户可以读取应用文件,但不需要root权限。
  • 已经明确环境变量、上传目录和需要持久化的数据位置。
  • 证书申请方式已经确定,或已经有可用的证书文件。
  • Nginx配置、PM2进程清单和应用上一版本可以备份。

用PM2托管Node.js进程

用ecosystem文件固定进程参数

把PM2配置放在应用目录之外或发布目录中,避免新版本覆盖运行参数。下面是一个可调整的示例:

module.exports = {
  apps: [
    {
      name: 'node-site',
      cwd: '/srv/node-site/current',
      script: './dist/server.js',
      exec_mode: 'fork',
      instances: 1,
      watch: false,
      autorestart: true,
      min_uptime: '10s',
      max_restarts: 10,
      restart_delay: 3000,
      kill_timeout: 5000,
      listen_timeout: 10000,
      max_memory_restart: '512M',
      out_file: '/home/deploy/.pm2/logs/node-site-out.log',
      error_file: '/home/deploy/.pm2/logs/node-site-error.log',
      time: true,
      env_production: {
        NODE_ENV: 'production',
        PORT: 3000,
        HOST: '127.0.0.1'
      }
    }
  ]
};

这里的 512M 只是进程重启保护的配置示例,不代表这台香港服务器必须使用这个值。它应结合应用的实际内存曲线、服务器可用内存和业务峰值调整。max_memory_restart 触发时会重启进程,可能中断正在处理的请求,因此不能把它当成扩容方案。

如果应用依赖环境文件,不要把数据库密码、密钥等内容直接写入ecosystem文件。可以让应用读取权限为600的环境文件:

sudo install -o deploy -g deploy -m 0600 /path/to/production.env \
  /srv/node-site/shared/.env

具体路径要与应用的加载方式一致。完成配置后,以部署用户启动:

sudo -iu deploy
cd /srv/node-site

pm2 start ecosystem.config.cjs --env production
pm2 status
pm2 logs node-site --lines 50

如果PM2显示进程在线,仍然要检查实际端口和本机访问:

ss -lntp | grep ':3000'
curl -fsS http://127.0.0.1:3000/healthz

让PM2脱离SSH会话后仍能恢复

PM2手工启动并不等于开机自启。先在部署用户下保存当前进程清单:

pm2 save

再生成systemd启动命令:

pm2 startup systemd -u deploy --hp /home/deploy

PM2会输出一条需要root权限执行的命令。应当完整复制并执行PM2实际输出的命令,不要自行猜测其中的Node.js路径。执行后再次保存进程清单:

pm2 save
exit

检查生成的服务名称和环境:

systemctl list-unit-files | grep pm2
systemctl status pm2-deploy
systemctl cat pm2-deploy

如果Node.js来自nvm,重点查看服务中的 PATH 是否包含Node.js和PM2所在目录。服务能够执行 pm2,不代表它一定能执行 node。这一步不通过,重启服务器后应用很可能不会恢复。

应用已经运行时,发布新版本不建议先停止再启动。可以使用平滑重载:

sudo -iu deploy
cd /srv/node-site

pm2 reload ecosystem.config.cjs --env production --update-env
pm2 status
exit

这要求应用能够正确处理关闭信号,并在退出前释放HTTP连接、定时器和数据库连接。如果应用收到SIGTERM后始终不退出,PM2会在 kill_timeout 到期后强制结束进程,重载过程就可能出现短暂中断。

让Nginx只做公网入口

先用HTTP验证反向代理

在证书配置完成前,可以先建立最小的HTTP反向代理,确认域名、Nginx和Node.js之间的链路没有问题:

server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;

    access_log /var/log/nginx/node-site.access.log;
    error_log  /var/log/nginx/node-site.error.log warn;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        proxy_read_timeout 60s;
    }
}

将配置放到发行版对应的站点目录,例如:

sudo cp /etc/nginx/sites-available/node-site \
  /etc/nginx/sites-available/node-site.bak.$(date +%F-%H%M%S)

sudo ln -sfn /etc/nginx/sites-available/node-site \
  /etc/nginx/sites-enabled/node-site

sudo nginx -t
sudo systemctl reload nginx

ln -sfn会替换同名符号链接,执行前应确认目标路径确实是站点配置链接。nginx -t失败时不要reload,先根据错误行修复配置。

访问域名并检查响应:

curl -I http://example.com
curl -fsS http://example.com/healthz

如果本机访问3000失败,先处理Node.js或PM2,不要反复修改Nginx。如果本机访问正常、域名访问失败,再检查域名解析、Nginx server_name、监听端口和系统或云侧防火墙规则。

HTTPS配置与反向代理头

证书已经签发后,将站点配置调整为HTTP跳转HTTPS,并在443端口代理到本机应用:

server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;

    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name example.com www.example.com;

    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    access_log /var/log/nginx/node-site.access.log;
    error_log  /var/log/nginx/node-site.error.log warn;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        proxy_read_timeout 60s;
    }
}

证书路径必须以实际签发工具和域名为准,不能照搬示例路径。修改后按以下顺序验证:

sudo nginx -t
sudo systemctl reload nginx

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

openssl s_client -connect example.com:443 \
  -servername example.com /dev/null |
  openssl x509 -noout -subject -dates

应用如果需要根据原始协议生成绝对URL,应读取 X-Forwarded-Proto,同时在框架中正确启用可信代理设置。否则应用可能误判请求仍为HTTP,造成登录回跳、Cookie安全属性或资源地址异常。

如果使用WebSocket,不能只依赖普通HTTP代理头。需要在Nginx的 http上下文中增加一次映射:

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

然后在对应的 location 中增加:

proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;

没有WebSocket需求时,不必为了套用模板增加这部分配置。

分开处理Nginx和PM2日志

PM2日志轮转

PM2默认会把标准输出和错误输出放在PM2目录下。生产环境至少要限制单个日志文件大小和保留数量。可以使用PM2的日志轮转模块:

sudo -iu deploy

pm2 install pm2-logrotate
pm2 set pm2-logrotate:max_size 20M
pm2 set pm2-logrotate:retain 14
pm2 set pm2-logrotate:compress true
pm2 set pm2-logrotate:dateFormat YYYY-MM-DD_HH-mm-ss
pm2 set pm2-logrotate:workerInterval 30

pm2 conf pm2-logrotate
pm2 save
exit

20M和14是示例边界,需要根据磁盘空间、日志量和排障保留周期调整。轮转数量越大,占用的磁盘也越多。应用自身如果还会写独立日志文件,应为那些文件单独配置轮转,不要以为PM2会自动管理它们。

不要同时用PM2轮转模块和systemd logrotate轮转同一批PM2日志。重复处理可能造成文件名冲突、日志丢失或排障困难。选定一种方式后,用以下命令核对:

sudo -iu deploy pm2 conf pm2-logrotate
sudo -iu deploy ls -lh /home/deploy/.pm2/logs

Nginx日志轮转

先检查系统是否已经包含Nginx轮转规则:

grep -R "/var/log/nginx" /etc/logrotate.conf /etc/logrotate.d 2>/dev/null

如果发行版已经提供覆盖 /var/log/nginx/*.log 的规则,不要再添加重复的通配规则。确认没有冲突后,可使用类似以下配置:

/var/log/nginx/node-site.access.log
/var/log/nginx/node-site.error.log {
    daily
    missingok
    rotate 14
    compress
    delaycompress
    notifempty
    create 0640 www-data adm
    sharedscripts
    postrotate
        if [ -s /run/nginx.pid ]; then
            kill -USR1 "$(cat /run/nginx.pid)"
        fi
    endscript
}

保存到 /etc/logrotate.d/node-site 后先做模拟检查:

sudo logrotate -d /etc/logrotate.d/node-site

-d只进行调试,不会实际轮转。Nginx使用USR1通知重新打开日志文件,不能简单照搬针对普通应用文件的 copytruncate 规则。实际强制轮转会改变日志文件,除非正在做轮转演练,否则不要在生产高峰期随意使用 logrotate -f。

证书续期必须验证“续期”和“加载”两件事

如果使用Certbot,先确认定时器或计划任务存在:

systemctl list-timers | grep -i certbot

然后执行演练:

sudo certbot renew --dry-run

证书成功续期不代表Nginx已经加载新证书。可以配置部署钩子,让续期完成后先检查配置,再reload:

sudo install -d -m 0755 /etc/letsencrypt/renewal-hooks/deploy

sudo tee /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh >/dev/null <<'EOF'
#!/bin/sh
set -eu

nginx -t
systemctl reload nginx
EOF

sudo chmod 0755 /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh

如果证书不是Certbot签发,使用证书提供方对应的续期任务,并确保任务最后执行:

sudo nginx -t
sudo systemctl reload nginx

不要在尚未确认所有域名和子域名都能正常HTTPS访问前启用HSTS。HSTS配置一旦被浏览器缓存,后续HTTP访问会被强制升级,回滚处理会更复杂。

备份配置、版本和恢复入口

可恢复的最小集合不是“备份整个服务器”,而是至少保存以下内容:

  • 当前版本和上一版本的应用构建产物。
  • ecosystem.config.cjs、环境变量文件及其权限信息。
  • Nginx站点配置和相关全局配置变更。
  • PM2保存的进程清单。
  • 证书和私钥,或能够重新签发证书的记录。
  • 上传文件以及应用实际依赖的持久化数据。

PM2进程清单先保存:

sudo -iu deploy pm2 save

备份关键配置时,可以使用下面的示例。证书目录和环境文件路径应按实际情况调整:

sudo install -d -m 0700 /var/backups/node-site

backup="/var/backups/node-site/node-site-$(date +%F-%H%M%S).tar.gz"

sudo tar -czf "$backup" \
    /etc/nginx/sites-available/node-site \
    /etc/nginx/sites-enabled/node-site \
    /home/deploy/.pm2/dump.pm2 \
    /srv/node-site/shared/.env \
    /etc/letsencrypt/live/example.com \
    /etc/letsencrypt/archive/example.com \
    /etc/letsencrypt/renewal/example.com.conf

sudo chmod 0600 "$backup"
sudo tar -tzf "$backup" >/dev/null

这个压缩包可能包含环境变量和TLS私钥,只能由受控账号读取;如果需要复制到其他存储,应先采用组织认可的加密方式。备份存在服务器本地不等于具备恢复能力,至少要定期在隔离环境验证压缩包可读、配置可用、上一版本能够启动。

应用涉及数据库时,代码回滚和数据回滚必须分开设计。新版本如果执行了不可逆的数据结构变更,单纯把Node.js代码切回上一版本并不一定能恢复服务,因此发布前要确认迁移是否向后兼容,并为数据建立独立的、一致性的备份方案。

资源边界不要用默认值代替判断

香港服务器上的Node.js服务是否稳定,除了进程不退出,还取决于内存、磁盘和文件描述符是否持续可用。部署后应观察以下基础指标:

free -h
df -h
df -ih
nproc
ss -s
sudo systemctl status nginx
sudo -iu deploy pm2 monit

几个容易被忽略的边界如下:

  • 实例数量:先使用一个PM2实例确认应用行为。只有在应用无状态、会话和临时数据处理方式明确,并且CPU或并发监测显示需要时,才考虑增加实例。盲目设置 instances: max 会放大内存消耗和日志量。
  • 内存重启阈值:max_memory_restart是保护措施,不是内存容量。阈值过低会频繁重启,过高则可能让系统进入内存紧张。
  • 日志磁盘:应用错误循环、访问日志暴增和压缩失败都可能快速消耗磁盘。应同时检查块空间和inode空间,不能只看 df -h。
  • 请求超时:proxy_read_timeout应匹配业务最长响应时间。长连接、文件上传和流式响应需要单独评估,不要统一设置成很大的值。
  • 文件描述符:连接数上升时,Nginx、Node.js和系统服务都可能受到文件描述符限制。先用 ulimit -n、服务配置和实际连接数定位,再决定是否调整,不能直接复制一组高值。
  • 上传大小:如果站点接收文件,应明确Nginx的 client_max_body_size、应用限制和磁盘容量,三者不一致时,用户可能在不同层面得到不同错误。

出现502或重启时按由外到内排查

先分辨是入口故障还是应用故障

按以下顺序检查,能减少反复改配置:

curl -I https://example.com
sudo nginx -t
sudo systemctl status nginx --no-pager
sudo tail -n 100 /var/log/nginx/node-site.error.log
  • HTTPS连接失败或证书信息不对,优先检查443监听、证书路径、域名和Nginx配置。
  • Nginx正常但返回502,继续检查本机上游:
curl -v http://127.0.0.1:3000/healthz
sudo -iu deploy pm2 status
sudo -iu deploy pm2 logs node-site --lines 100
  • 本机3000端口连接失败,说明问题集中在PM2、Node.js启动参数、环境变量、端口占用或应用自身。
  • 本机健康检查正常但外部仍失败,再检查Nginx server_name、代理路径、请求头、域名解析和防火墙。

进程反复重启时不要只执行restart

查看PM2状态和错误日志:

sudo -iu deploy pm2 status
sudo -iu deploy pm2 describe node-site
sudo -iu deploy pm2 logs node-site --err --lines 200

常见线索包括:

  • Cannot find module:发布目录或构建产物路径错误。
  • EADDRINUSE:端口已被其他进程占用。
  • 环境变量缺失:systemd启动环境与SSH登录环境不一致。
  • 反复达到内存阈值:先确认是否存在内存泄漏或日志、缓存增长,再调整阈值。
  • 启动后很快退出:检查应用是否正确绑定 127.0.0.1:3000,以及健康检查是否使用了错误端口。

不要在没有保存日志的情况下执行会清空日志的操作,也不要通过不断提高 max_memory_restart 来掩盖进程持续增长。

发布失败时的回滚路径

应用版本回滚

如果使用独立发布目录,假设当前版本和上一版本分别为:

/srv/node-site/releases/2026-09-25-1000
/srv/node-site/releases/2026-09-24-1800
/srv/node-site/current -> /srv/node-site/releases/2026-09-25-1000

发布失败时,先确认上一版本目录完整,再切换符号链接并重载PM2:

readlink -f /srv/node-site/current
ls -ld /srv/node-site/releases/2026-09-24-1800

sudo ln -sfn /srv/node-site/releases/2026-09-24-1800 \
  /srv/node-site/current

sudo -iu deploy pm2 reload /srv/node-site/ecosystem.config.cjs \
  --env production --update-env

随后验证:

curl -fsS http://127.0.0.1:3000/healthz
curl -fsS https://example.com/healthz
sudo -iu deploy pm2 status

如果新版本已经执行了不兼容的数据迁移,不能把上述代码回滚当作完整恢复方案,应按数据备份和迁移回退方案处理。

Nginx配置回滚

改动Nginx前先保留副本:

sudo cp -a /etc/nginx/sites-available/node-site \
  /etc/nginx/sites-available/node-site.bak.$(date +%F-%H%M%S)

发现新配置导致语法错误或业务异常时,恢复经过确认的副本,然后必须先测试再reload:

sudo cp -a /etc/nginx/sites-available/node-site.bak.YYYY-MM-DD-HHMMSS \
  /etc/nginx/sites-available/node-site

sudo nginx -t
sudo systemctl reload nginx

恢复后重新检查证书、反向代理地址、日志路径和 server_name,避免只恢复了语法,却保留了错误的上游端口。

容易被忽略的复盘项

部署完成后,现场验收不应只看首页能否打开,还应确认:

  • SSH退出后,PM2进程仍在运行。
  • 服务器重启后,systemd能够恢复PM2及其进程清单。
  • Node.js只监听本机地址,公网无法直接访问3000端口。
  • nginx -t通过,HTTP跳转HTTPS,证书主题和有效期符合预期。
  • 本机健康检查、外部健康检查和真实业务页面结果一致。
  • PM2日志和Nginx日志都有轮转规则,且没有两个工具重复管理同一文件。
  • df -h和df -ih仍有余量,日志压缩和保留策略符合磁盘边界。
  • 证书续期演练成功,续期后Nginx会重新加载证书。
  • 当前版本、上一版本、Nginx配置、PM2清单和敏感配置都能从备份中找到。
  • 回滚命令已经在低风险时间验证过,而不是等故障发生后临时拼接。

当入口、进程、日志、证书、备份和资源边界都能独立验证时,Node.js网站部署到香港服务器后才不只是“当前能访问”,而是具备持续运行和出现异常后快速恢复的基础。

目录结构
全文