本地跑得好好的服务,一上服务器就“水土不服”?SSH 断了服务就没了、nginx 502、怎么没有 HTTPS 小锁——这条部署链路里的每个坑我都踩过。这篇按从零到线上的顺序,给出一份可以直接照抄的 Node 服务部署记录。
一、部署架构先画清楚
用户请求
│
▼
Nginx :80/:443 ← 对外入口:域名、HTTPS、静态资源
│ 反向代理
▼
pm2 守护的 Node 服务 ← 127.0.0.1:3000(不直接暴露公网)
│
▼
数据库 / Redis ← 内网或受保护访问
- 浏览器只访问 Nginx,Node 监听在本地端口,由 Nginx 转发;
- 这样静态资源(js/css/img)由 Nginx 直接返回,性能更好;HTTPS、Gzip、限流都在 Nginx 层做;
- Node 进程由 pm2 守护:崩溃自动拉起、开机自启、日志统一管理。
二、服务器基础准备
# 1. 建议用非 root 用户操作,SSH 用密钥登录并禁用密码
adduser deploy
usermod -aG sudo deploy
# 本机生成密钥:ssh-keygen -t ed25519,把公钥写入 ~/.ssh/authorized_keys
# 2. 安装 Node(推荐 nvm,避免版本混乱)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
nvm install 20 # LTS 版本
# 3. 安装 pm2(全局)
npm i -g pm2
生产环境尽量用 Node LTS 版本,不要追新;同一台机器有多个项目时 nvm + .nvmrc 能锁定每个项目的版本,避免“我本地能跑”的版本差异问题。
三、代码上线与 pm2 守护
# 1. 拉取代码并安装依赖(用 pnpm/npm 按项目来)
cd /www/wwwroot/my-app
git pull
pnpm install --frozen-lockfile
pnpm build # 编译出 dist/
# 2. 用 ecosystem 文件启动,配置可提交进仓库
# ecosystem.config.js
module.exports = {
apps: [{
name: 'my-app',
script: 'dist/main.js',
instances: 1, // 多核再考虑 cluster 模式
autorestart: true,
max_memory_restart: '500M', // 内存超过自动重启,防泄漏拖垮机器
env: { NODE_ENV: 'production', PORT: 3000 },
out_file: './logs/pm2-out.log',
error_file: './logs/pm2-error.log',
merge_logs: true,
}],
};
# 3. 启动与常用操作
pm2 start ecosystem.config.js
pm2 reload my-app # 平滑重启(推荐发布用 reload 而非 restart)
pm2 status # 看进程状态
pm2 logs my-app # 看日志
pm2 save && pm2 startup # 保存进程列表 + 生成开机自启
reload 和 restart 的区别:restart 直接杀掉重启,会中断在途请求;reload 是零停机地滚动重启(cluster 模式逐个重启,单实例时也会先起新进程再交接)。发布用 reload,体验好一个量级。
四、Nginx 反向代理配置
# /etc/nginx/sites-available/my-app.conf
server {
listen 80;
server_name example.com www.example.com;
# 前端静态资源(如 SPA 构建产物)直接交给 Nginx
root /www/wwwroot/my-app/dist; # 如果有独立前端
location / {
proxy_pass http://127.0.0.1:3000; # 代理到 Node
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;
# WebSocket 支持(如聊天/实时通知)
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 60s;
}
}
# 启用并检查配置
ln -s /etc/nginx/sites-available/my-app.conf /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx
X-Forwarded-For / X-Forwarded-Proto必须转发:Node 里取真实 IP、判断 HTTPS 都靠它,否则拿到的一律是 127.0.0.1;- 改完配置先
nginx -t校验语法,别 reload 一个坏配置把整个站点带崩。
五、HTTPS:免费证书让站点带上小锁
# Let's Encrypt + certbot,免费且自动续期
apt install certbot python3-certbot-nginx
certbot --nginx -d example.com -d www.example.com
# certbot 会自动改 Nginx 配置并设置续期任务,可手动验证续期
certbot renew --dry-run
- 证书有效期 90 天,自动续期务必配好(certbot 的 systemd timer / cron);
- HTTP 请求 301 跳 HTTPS,用 certbot 插件能一键完成;
- 定期盯一下续期是否成功,证书过期当天整站报错是常见事故。
六、平滑发布与回滚
# 发布 = 三步
cd /www/wwwroot/my-app
git pull # 1. 更新代码
pnpm install && pnpm build # 2. 安装 + 构建
pm2 reload my-app # 3. 平滑重启
# 新版本有问题?回滚也一样快
git checkout <上一个发布tag>
pnpm build
pm2 reload my-app
给每次发布打
tag(如 v1.2.0),配合 release 分支,回滚就是 git checkout v1.1.0 再 reload——比“从回收站找回 dist”靠谱一万倍。敏感配置(数据库密码、密钥)走 .env + 环境变量,绝不提交进仓库。七、上线后的健康检查清单
- 进程:
pm2 status全 online,重启次数(restarts)不持续上涨; - 日志:out/error 日志无持续报错,配
pm2-logrotate防止日志撑爆磁盘; - 网络:本地
curl -I https://example.com返回 200 与正确响应头; - 安全:防火墙只开放 80/443(与 SSH),数据库端口不对公网开放;
- 监控:服务器厂商的基础监控 + 进程内存告警,出事能第一时间知道。
这套链路跑通一次之后,往后每次发布就是三行命令的事。部署稳定了,就可以把精力放到性能与体验上了——那也是另一个话题的起点。