发布于

Next.js 自托管实践:PM2 + Nginx 反向代理

作者
  • avatar
    姓名
    小欧

为什么不直接用 Vercel

Vercel 部署 Next.js 确实是一键的事,但对个人项目来说,自托管有几个实际好处:

  • 服务器本来就有,不用额外花钱
  • 能完整掌握 TLS、反代、进程守护这些细节
  • 想挂别的服务(API、Python 应用)时可以统一在同一个 Nginx 下做路径分发

代价是要自己处理构建、进程守护、HTTPS 续期这些事。

一、构建产物

自托管跑的是生产构建:

npm ci
npm run build   # 产出 .next/
npm run serve   # 等价于 next start,监听 3000

注意 next devnext start 的差别:前者是开发服务器,带 HMR、性能差、绝对不能上生产。

二、进程守护:为什么选 PM2

裸跑 next start 的问题是:SSH 一断,进程就没了;服务器重启后也不会自动拉起。

PM2 解决这些问题:

pm2 start npm --name "my-site" -- run serve
pm2 save          # 保存进程列表
pm2 startup       # 生成开机自启脚本

常用命令:

命令作用
pm2 list查看所有进程状态
pm2 logs my-site实时日志
pm2 reload my-site平滑重载(不中断请求)
pm2 monit资源监控面板

关键原则:一个应用只能有一套进程管理器。如果 systemd 里同时还有一个 unit 在拉起同一个应用,两者会互相抢占端口,日志也会混乱。

三、Nginx 反向代理

server {
    listen 80;
    server_name example.com;

    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;
    }
}

几个容易忽略的点:

  1. proxy_set_header Host $host 必须带,否则 Next.js 生成的绝对 URL(比如 OG 图、canonical)会指向 127.0.0.1
  2. X-Forwarded-Proto 用于让应用知道真实协议是 https,否则会出现混合内容告警
  3. WebSocket 升级需要额外的 Upgrade / Connection

四、HTTPS

用 certbot 自动签发并续期:

certbot --nginx -d example.com

它会自动改写 Nginx 配置并注册续期定时任务。验证续期是否生效:

certbot renew --dry-run

五、踩坑记录:80 和 443 配置不对称

这是最隐蔽的一个问题。现象是:

  • https://example.com 访问正常
  • http://example.com 显示服务器默认页

原因是 443 的 server 块里配了 location / { proxy_pass ... },而 80 的 server 块里没有。请求走了静态文件目录,自然返回默认页。

排查方法是分层定位:

# 1. 应用本身正常吗?
curl -I http://127.0.0.1:3000

# 2. 反代层正常吗?(带上 Host 头绕过 DNS)
curl -I -H "Host: example.com" http://127.0.0.1/

# 3. 两份配置一致吗?
diff <(sed -n '/server {/,/}/p' conf.d/http.conf) \
     <(sed -n '/server {/,/}/p' conf.d/https.conf)

第三步通常一眼就能看出问题。

六、上线检查清单

  • pm2 list 中进程状态为 online,重启次数为 0
  • 服务器重启后服务能自动恢复
  • HTTP 自动跳转 HTTPS
  • 反向代理透传了 HostX-Forwarded-Proto
  • 静态资源带长缓存(immutable, max-age=31536000
  • 只存在一套进程管理器

自托管不复杂,难的是把每个环节都验证一遍