- 发布于
Next.js 自托管实践:PM2 + Nginx 反向代理
- 作者

- 姓名
- 小欧
为什么不直接用 Vercel
Vercel 部署 Next.js 确实是一键的事,但对个人项目来说,自托管有几个实际好处:
- 服务器本来就有,不用额外花钱
- 能完整掌握 TLS、反代、进程守护这些细节
- 想挂别的服务(API、Python 应用)时可以统一在同一个 Nginx 下做路径分发
代价是要自己处理构建、进程守护、HTTPS 续期这些事。
一、构建产物
自托管跑的是生产构建:
npm ci
npm run build # 产出 .next/
npm run serve # 等价于 next start,监听 3000
注意 next dev 和 next 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;
}
}
几个容易忽略的点:
proxy_set_header Host $host必须带,否则 Next.js 生成的绝对 URL(比如 OG 图、canonical)会指向127.0.0.1X-Forwarded-Proto用于让应用知道真实协议是 https,否则会出现混合内容告警- 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
- 反向代理透传了
Host与X-Forwarded-Proto - 静态资源带长缓存(
immutable, max-age=31536000) - 只存在一套进程管理器
自托管不复杂,难的是把每个环节都验证一遍。