Ubuntu 18.04 Node.js生产部署:PM2+NGINX全栈配置指南
1. 项目概述:这不是“装个Node.js就完事”的简单操作
你搜到“Cómo configurar una aplicación de Node.js para producción en Ubuntu 18.04”这个西班牙语标题时,大概率正卡在某个深夜——服务器上跑着一个本地调试好好的Express或Koa服务,一换到Ubuntu 18.04的VPS上, node app.js 能跑通,但关掉SSH终端就断;用 curl localhost:3000 能返回HTML,但从浏览器输IP地址却打不开;或者更糟:刚部署完Vue前端+Node后端,Nginx反向代理配了八遍,日志里全是 502 Bad Gateway 。这不是你代码写得有问题,而是你正在踩一条绝大多数Node.js新手必经的“生产环境认知断层线”:开发环境和生产环境之间,隔着的不是端口号,而是一整套系统级工程实践。
我从2014年开始在Ubuntu上部署Node应用,经历过从forever、supervisor到PM2的迁移,也亲手重配过不下二十台Ubuntu 16.04/18.04/20.04的生产服务器。Ubuntu 18.04这个版本特别典型——它自带的systemd已经足够成熟,但又不像20.04那样默认集成最新Node源;它的Nginx版本(1.14.0)稳定得让人安心,但也意味着你得手动处理WebSocket升级头、大文件上传限制这些老问题。而PM2,它绝不是“多加个 -i max 就能自动集群”的黑盒,它的进程守护逻辑、日志轮转策略、内存溢出自动重启阈值,全得你亲手调教。这篇文章不讲“Node.js是干啥的”这种入门科普,也不堆砌 apt install nodejs 这种命令流水账。我要带你一层层剥开:为什么必须用PM2而不是 & 后台运行?为什么Nginx不能只做反向代理,还得当SSL终结者和静态资源分发器?为什么Ubuntu 18.04的 /etc/systemd/system/ 目录里,一个 .service 文件的 RestartSec=10 比 Restart=always 更能救你的命?我会把每个配置项背后的Linux内核机制、HTTP协议细节、Node事件循环特性都摊开来讲,让你下次再看到 EADDRINUSE 或 EMFILE 错误时,第一反应不是百度,而是直接去查 ulimit -n 和 sysctl net.core.somaxconn 。适合谁看?如果你已经能用 npm init 建项目、写个REST API、甚至用Webpack打包前端,但一到上线就手足无措,那这篇就是为你写的。它不承诺“5分钟搞定”,但保证你读完后,能独立判断:这个报错该查Nginx日志还是PM2日志?这个性能瓶颈该调Node参数还是系统参数?这才是真正在Ubuntu 18.04上跑Node.js生产应用的底层能力。
2. 整体架构设计与方案选型逻辑
2.1 为什么必须放弃 node app.js & 和 nohup ?
很多教程开头就写“用 nohup node app.js & 启动”,这在开发测试阶段确实省事,但在Ubuntu 18.04生产环境中,这是埋下三颗定时炸弹:
第一颗是 进程生命周期失控 。 nohup 只是把 stdout 重定向到 nohup.out ,它完全不管Node进程崩溃后怎么办。你写了个异步数据库查询没加 .catch() ,Promise rejection未处理,Node进程瞬间退出, nohup.out 里可能只留下半行日志,而你的API已经挂了两小时没人发现。Ubuntu 18.04的systemd虽然强大,但它不会主动监控一个被 nohup 扔进后台的进程是否还活着——它只认得自己启动的服务单元。
第二颗是 资源泄漏黑洞 。Node.js的 fs.watch() 、 net.createConnection() 这类API,在进程异常退出时,内核里的文件描述符(file descriptor)和socket连接不会立刻释放。Ubuntu 18.04默认的 ulimit -n 是1024,一个长期运行的Node服务如果每秒建立10个连接又没正确关闭,几小时后就会触发 EMFILE 错误,新请求全部被拒。 nohup 对此毫无感知,它连进程PID都懒得记,更别说帮你回收资源了。
第三颗是 运维可见性归零 。你想看应用实时日志? tail -f nohup.out 。想查内存占用? ps aux | grep node 。想平滑重启?先 ps aux | grep node | awk '{print $2}' | xargs kill -9 ,再重新 nohup ——这期间所有请求都会502。而生产环境要求的是:日志按天轮转、内存超限自动重启、零停机更新。这些都不是 & 符号能解决的。
所以PM2成了唯一合理选择。它不是简单的进程管理器,而是一个为Node.js深度定制的运行时平台。它内置的 --watch 能监听文件变化并热重载, --max-memory-restart 512M 能在RSS内存超限时自动重启进程, pm2 logs --raw 能聚合所有实例日志并高亮错误行。更重要的是,PM2能生成systemd服务文件,让Ubuntu 18.04的systemd真正接管Node进程——这意味着 sudo systemctl restart myapp 会触发PM2的优雅重启流程,而不是粗暴 kill -9 。
2.2 Nginx为何不可替代?反向代理只是它最基础的功能
很多人以为Nginx在这里只干一件事:把 https://myapp.com/api 的请求转发给 http://localhost:3000 。这就像说“汽车只是四个轮子加个铁壳”。在Ubuntu 18.04生产环境中,Nginx实际承担着五层关键职责:
第一层:SSL/TLS终结者 。Node.js原生支持HTTPS,但每次TLS握手都要消耗CPU进行非对称加密运算。Ubuntu 18.04的Nginx 1.14.0配合OpenSSL 1.1.1,能利用硬件AES-NI指令集加速加解密,把TLS卸载到Nginx层,让Node进程专注业务逻辑。而且Let's Encrypt证书的自动续期(通过 certbot --nginx )只能由Nginx配合完成,Node.js自己搞这套太重。
第二层:静态资源分发器 。你的Vue/React前端打包后有 /static/js/app.xxx.js 、 /static/css/app.xxx.css ,这些文件根本不需要Node.js解析路由、执行JavaScript。Nginx用 location /static/ { alias /var/www/myapp/dist/static/; } 直接从磁盘读取并返回,响应时间从Node的20ms降到Nginx的2ms,同时释放Node的Event Loop。
第三层:连接池管理者 。Node.js单进程能维持的TCP连接数受 ulimit -n 限制,但Nginx作为反向代理,可以用 proxy_buffering on 开启缓冲,把客户端慢速连接(比如移动网络)的数据先收满再一次性推给Node,避免Node被大量空闲连接拖垮。Ubuntu 18.04的默认 net.core.somaxconn=128 太小,Nginx的 listen 80 backlog=4096 能撑住突发流量。
第四层:安全过滤网 。Nginx的 limit_req zone=api burst=10 nodelay 能防接口刷量, add_header X-Content-Type-Options nosniff 能防MIME类型混淆攻击, client_max_body_size 10M 能拦住恶意大文件上传。这些规则写在Nginx配置里,比在每个Express路由里 if (req.headers['content-length'] > 10e6) return res.status(413) 要高效一万倍。
第五层:WebSocket桥梁 。Node.js的 ws 库需要客户端升级HTTP连接,Nginx必须显式配置 proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; 才能透传WebSocket帧。漏掉这一行,你的聊天室或实时通知功能永远连不上。
所以,Nginx和PM2不是“可选配件”,而是Ubuntu 18.04上Node.js生产环境的左右手:PM2管进程生死,Nginx管线上的所有流量。
2.3 Ubuntu 18.04的特殊性:LTS版本的双刃剑
Ubuntu 18.04是2018年4月发布的长期支持版(LTS),官方支持到2023年4月(ESM扩展支持到2028年)。这个“稳定”背后有明确代价:它默认仓库里的软件版本普遍偏旧。比如 apt install nodejs 装的是v8.10.0,而当前LTS版Node.js已是v18.x。直接用旧版Node.js跑现代框架(如NestJS v10需要Node v16+)会报 SyntaxError: Unexpected token 'export' ——因为ES2015模块语法不被支持。
解决方案不是暴力编译新版Node,而是用 NodeSource官方源 。它提供针对Ubuntu各版本优化的二进制包,安装后 node -v 直接显示v18.19.0,且 npm 版本同步更新。为什么不用 nvm ?因为 nvm 是用户级工具,它装的Node只对当前shell生效,而systemd服务需要系统级Node环境。 nvm 在 /home/deploy/.nvm/versions/node/v18.19.0/bin/node ,systemd找不到这个路径,除非你写死绝对路径——这违背了“环境一致性”原则。
另一个坑是 systemd的默认限制 。Ubuntu 18.04的systemd对服务单元有严格资源约束: DefaultLimitNOFILE=4096 (文件描述符上限), DefaultLimitNPROC=384 (进程数上限)。Node.js应用常开Redis连接池(默认200连接)、数据库连接池(默认10)、日志轮转句柄(每个日志文件1个),很容易突破 NPROC 限制。所以PM2生成的systemd服务文件里,必须显式覆盖这些值: LimitNOFILE=65536 、 LimitNPROC=65536 。漏掉这个,你的应用在高并发时会静默失败, journalctl -u myapp 里只显示 fork failed: Resource temporarily unavailable 。
最后是 时区与Locale陷阱 。Ubuntu 18.04默认 locale 是 C.UTF-8 ,但某些Node.js库(如 date-fns-tz )依赖系统时区数据。 dpkg-reconfigure tzdata 选Asia/Shanghai后,必须重启PM2服务,否则 new Date().toLocaleString() 仍显示UTC时间。这不是Node.js的bug,是Linux glibc的时区缓存机制——systemd服务启动时读取一次 /etc/timezone ,之后就不更新了。
3. 核心细节解析与实操要点
3.1 Node.js安装:绕过apt仓库,直连NodeSource
Ubuntu 18.04的 apt 仓库里Node.js版本太老,直接 sudo apt install nodejs 会装v8.10.0,连 async/await 都不支持。必须用NodeSource官方源,它提供deb包,和系统apt完美集成,还能自动处理依赖(如 libssl1.1 )。
第一步,清理旧版残留(如果之前装过):
sudo apt remove nodejs npm
sudo apt autoremove
# 删除可能存在的nvm安装
rm -rf ~/.nvm
第二步,导入NodeSource GPG密钥并添加仓库。注意:Ubuntu 18.04代号是 bionic ,不是 focal 或 jammy ,选错代号会导致 apt update 报404:
# 下载并执行安装脚本(它会自动检测Ubuntu版本)
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
# 如果脚本失效,手动添加(推荐):
echo "deb https://deb.nodesource.com/node_18.x bionic main" | sudo tee /etc/apt/sources.list.d/nodesource.list
echo "deb-src https://deb.nodesource.com/node_18.x bionic main" | sudo tee -a /etc/apt/sources.list.d/nodesource.list
# 导入GPG密钥(关键!否则apt update会报GPG error)
wget -qO - https://deb.nodesource.com/gpgkey/nodesource.gpg.key | sudo apt-key add -
第三步,更新索引并安装:
sudo apt update
sudo apt install -y nodejs
# 验证安装
node -v # 应输出 v18.19.0
npm -v # 应输出 9.9.2
提示:
setup_lts.x脚本会自动安装nodejs和npm,无需单独apt install npm。如果npm -v报错,检查/usr/bin/npm是否存在,不存在则sudo ln -s /usr/bin/nodejs /usr/bin/npm(极少见)。
为什么不用 nvm ? nvm 是为开发者设计的版本管理器,它把不同Node版本装在用户家目录,通过修改 PATH 环境变量切换。但systemd服务以 root 或专用用户(如 deploy )运行,它启动时的 PATH 不包含 ~/.nvm/versions/node/v18.19.0/bin 。你可以在 .service 文件里写 Environment="PATH=/home/deploy/.nvm/versions/node/v18.19.0/bin:/usr/local/bin:/usr/bin:/bin" ,但这违反了“环境一致性”原则——一旦 nvm 路径变更或用户家目录迁移,服务就崩。NodeSource的deb包把 node 和 npm 装在 /usr/bin/ ,所有用户、所有服务都能直接调用,这才是生产环境该有的确定性。
3.2 PM2深度配置:不只是 pm2 start app.js
PM2默认配置对生产环境来说太“温柔”。你需要手动调整六个核心参数,否则它会在半夜三点默默重启你的服务,还不告诉你原因。
第一,进程守护模式 。 pm2 start app.js 默认是 fork 模式,即用 child_process.fork() 启动。这对调试友好,但生产环境推荐 cluster 模式,利用多核CPU:
# 启动4个实例(根据CPU核心数调整)
pm2 start app.js -i 4 --name "myapp"
# 查看集群状态
pm2 show myapp
-i 4 会启动4个Node进程,PM2内置的负载均衡器(Round Robin)自动分发请求。别盲目设 -i max ——Ubuntu 18.04的 ulimit -u (用户最大进程数)默认是384, max 会尝试启动 nproc 个进程,可能超出限制。用 nproc 命令查实际CPU数,再减1留作系统余量。
第二,内存监控与自动重启 。Node.js内存泄漏很隐蔽, process.memoryUsage().heapUsed 持续增长就是信号。PM2的 --max-memory-restart 参数能救命:
pm2 start app.js -i 4 --max-memory-restart 512M --name "myapp"
当任一实例的RSS内存超过512MB,PM2会优雅重启它(先发 SIGINT 让Node处理完当前请求,再 SIGKILL 强制结束)。阈值怎么定?用 pm2 monit 观察几天,找到内存使用峰值的1.2倍。比如峰值是380MB,就设 450M ,留点缓冲。
第三,日志管理 。默认日志全打在 ~/.pm2/logs/ ,不轮转、不压缩,几个月后占满磁盘。必须启用日志轮转:
# 安装日志轮转模块
pm2 install pm2-logrotate
# 配置:每天切割,保留30天,压缩旧日志
pm2 set pm2-logrotate:maxFileSize 10M
pm2 set pm2-logrotate:retain 30
pm2 set pm2-logrotate:compress true
pm2 set pm2-logrotate:dateFormat YYYY-MM-DD_HH-mm-ss
这样 myapp-out.log 每天0点切为 myapp-out.log.YYYY-MM-DD ,超过30天自动删除, .gz 压缩后体积减少70%。
第四,环境变量隔离 。开发环境用 .env ,生产环境必须用PM2的 --env 参数注入,避免敏感信息泄露:
pm2 start app.js -i 4 --env production --name "myapp"
# 在app.js里用 process.env.NODE_ENV 判断
if (process.env.NODE_ENV === 'production') {
// 加载生产配置
}
--env production 会加载 ecosystem.config.js 里 production 环境的变量,比在代码里 require('dotenv').config({ path: '.env.production' }) 更安全—— .env 文件万一被误提交到Git,密钥就暴露了。
第五,启动脚本固化 。 pm2 start 命令不能只存在你脑子里,必须导出为 ecosystem.config.js ,这是PM2的“基础设施即代码”:
// ecosystem.config.js
module.exports = {
apps: [{
name: 'myapp',
script: './app.js',
instances: 4,
exec_mode: 'cluster',
watch: false, // 生产环境禁用热重载
max_memory_restart: '512M',
env: {
NODE_ENV: 'development',
PORT: 3000
},
env_production: {
NODE_ENV: 'production',
PORT: 3000,
DATABASE_URL: 'postgresql://user:pass@localhost:5432/mydb'
}
}],
deploy: {
production: {
user: 'deploy',
host: '192.168.1.100',
ref: 'origin/main',
repo: 'git@github.com:me/myapp.git',
path: '/var/www/myapp',
'post-deploy': 'npm install && pm2 reload ecosystem.config.js --env production'
}
}
};
然后用 pm2 start ecosystem.config.js --env production 启动。这样所有配置都在代码里,团队新人 git clone 后 pm2 start 就能一键部署。
第六,systemd集成 。让Ubuntu 18.04的systemd接管PM2,实现开机自启和统一管理:
# 生成systemd服务文件
pm2 startup systemd -u deploy --hp /home/deploy
# 这会输出一行命令,复制执行(它会创建 /etc/systemd/system/pm2-deploy.service)
sudo env PATH=$PATH:/usr/bin /usr/lib/node_modules/pm2/bin/pm2 startup systemd -u deploy --hp /home/deploy
# 保存当前PM2进程列表到dump文件
pm2 save
# 重启systemd服务
sudo systemctl daemon-reload
sudo systemctl start pm2-deploy
现在 sudo systemctl status pm2-deploy 能看到PM2服务状态, sudo journalctl -u pm2-deploy -f 能看PM2自身日志。这才是生产环境该有的可观测性。
3.3 Nginx核心配置:超越基础反向代理
Nginx配置不是把 proxy_pass http://localhost:3000; 塞进去就完事。Ubuntu 18.04的Nginx 1.14.0需要至少七个关键块才能撑起生产流量。
第一,上游服务器定义(Upstream) 。不要在每个 server 块里硬写 proxy_pass http://localhost:3000; ,用 upstream 抽象出后端集群,便于未来横向扩展:
# /etc/nginx/conf.d/myapp.conf
upstream myapp_backend {
server 127.0.0.1:3000 weight=10 max_fails=3 fail_timeout=30s;
# 如果未来加第二台Node服务器,加一行:
# server 192.168.1.101:3000 weight=5;
keepalive 32; # 保持长连接,减少TCP握手开销
}
weight=10 表示这台服务器接收10份流量, max_fails=3 表示连续3次健康检查失败就剔除, fail_timeout=30s 是剔除后30秒内不再发请求。 keepalive 32 让Nginx和Node保持最多32个空闲连接,避免频繁建连。
第二,SSL/TLS配置 。用Let's Encrypt免费证书, certbot 会自动修改Nginx配置:
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d myapp.com -d www.myapp.com
生成的配置会包含:
listen 443 ssl http2; # http2提升性能
ssl_certificate /etc/letsencrypt/live/myapp.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/myapp.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3; # 禁用不安全的TLSv1.0/1.1
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
# OCSP Stapling加速证书验证
ssl_stapling on;
ssl_stapling_verify on;
resolver 1.1.1.1 8.8.8.8 valid=300s;
resolver_timeout 5s;
第三,反向代理核心参数 。这些决定了Nginx如何与Node交互:
location / {
proxy_pass http://myapp_backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade"; # WebSocket必需
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_set_header X-Nginx-Proxy true;
# 超时设置,防止Node慢请求拖垮Nginx
proxy_connect_timeout 5s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# 缓冲区调优
proxy_buffering on;
proxy_buffer_size 128k;
proxy_buffers 4 256k;
proxy_busy_buffers_size 256k;
}
proxy_http_version 1.1 是必须的,HTTP/1.0不支持 Connection: upgrade ; proxy_buffering on 开启缓冲,避免Node响应慢时Nginx等待; proxy_buffers 设大些能减少磁盘IO。
第四,静态资源直出 。把前端打包文件交给Nginx,别让Node处理:
# 前端dist目录假设在 /var/www/myapp/dist
location /static/ {
alias /var/www/myapp/dist/static/;
expires 1y;
add_header Cache-Control "public, immutable";
}
location / {
# Vue Router history模式:所有非API请求都返回index.html
try_files $uri $uri/ /index.html;
}
expires 1y 让浏览器缓存一年, Cache-Control 头确保CDN也缓存。 try_files 是Vue/React Router的救命配置,否则刷新页面会404。
第五,安全加固 。加几行头就能防常见攻击:
# 防点击劫持
add_header X-Frame-Options "DENY" always;
# 防MIME类型混淆
add_header X-Content-Type-Options "nosniff" always;
# 防XSS
add_header X-XSS-Protection "1; mode=block" always;
# 内容安全策略(CSP),根据你的前端资源调整
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data:;" always;
第六,速率限制 。防机器人刷接口:
# 定义限流区域:每个IP每秒最多10个请求
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
server {
location /api/ {
limit_req zone=api burst=20 nodelay; # 突发20个请求立即处理,超了503
proxy_pass http://myapp_backend;
}
}
第七,日志格式定制 。默认日志不包含真实客户端IP(只有127.0.0.1),必须用 $http_x_forwarded_for :
log_format main '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'$http_x_forwarded_for $request_time $upstream_response_time';
access_log /var/log/nginx/myapp_access.log main;
$http_x_forwarded_for 取自 X-Forwarded-For 头, $request_time 是Nginx处理总时间, $upstream_response_time 是Node响应时间,对比这两个值就能定位瓶颈在Nginx还是Node。
4. 实操过程与核心环节实现
4.1 从零开始:完整部署流程实录
假设你有一个现成的Express应用,目录结构如下:
myapp/
├── app.js # 入口文件
├── package.json
├── dist/ # Vue前端打包目录
└── ecosystem.config.js
步骤1:创建专用部署用户(安全基线)
绝不允许用 root 用户部署。创建 deploy 用户,并禁用密码登录(只用SSH密钥):
sudo adduser deploy --disabled-password
sudo usermod -aG sudo deploy
# 切换到deploy用户
sudo su - deploy
# 生成SSH密钥(如果还没有)
ssh-keygen -t rsa -b 4096 -C "deploy@myapp.com"
# 将公钥添加到GitHub/GitLab
cat ~/.ssh/id_rsa.pub
步骤2:配置Node.js和PM2
在 deploy 用户下执行:
# 安装Node.js(按3.1节方法)
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
sudo apt update && sudo apt install -y nodejs
# 全局安装PM2
npm install -g pm2
# 验证
node -v && npm -v && pm2 -v
步骤3:克隆代码并安装依赖
mkdir -p /var/www/myapp
cd /var/www/myapp
git clone https://github.com/you/myapp.git .
# 安装生产依赖(跳过devDependencies)
npm ci --only=production
# 创建日志目录
mkdir -p /var/www/myapp/logs
步骤4:配置PM2并启动
# 确保ecosystem.config.js存在(按3.2节内容)
# 启动应用
pm2 start ecosystem.config.js --env production
# 保存进程列表
pm2 save
# 生成systemd服务
pm2 startup systemd -u deploy --hp /home/deploy
# 执行输出的命令(类似下面这行)
sudo env PATH=$PATH:/usr/bin /usr/lib/node_modules/pm2/bin/pm2 startup systemd -u deploy --hp /home/deploy
# 重启systemd
sudo systemctl daemon-reload
sudo systemctl start pm2-deploy
步骤5:配置Nginx
创建 /etc/nginx/conf.d/myapp.conf :
upstream myapp_backend {
server 127.0.0.1:3000 weight=10 max_fails=3 fail_timeout=30s;
keepalive 32;
}
server {
listen 80;
server_name myapp.com www.myapp.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name myapp.com www.myapp.com;
ssl_certificate /etc/letsencrypt/live/myapp.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/myapp.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
# 静态资源
location /static/ {
alias /var/www/myapp/dist/static/;
expires 1y;
add_header Cache-Control "public, immutable";
}
# 前端路由
location / {
root /var/www/myapp/dist;
try_files $uri $uri/ /index.html;
}
# API代理
location /api/ {
proxy_pass http://myapp_backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
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_connect_timeout 5s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
proxy_buffering on;
proxy_buffer_size 128k;
proxy_buffers 4 256k;
proxy_busy_buffers_size 256k;
}
# 日志
access_log /var/log/nginx/myapp_access.log main;
error_log /var/log/nginx/myapp_error.log warn;
}
测试并重载Nginx:
sudo nginx -t # 检查语法
sudo systemctl reload nginx
步骤6:申请SSL证书
sudo certbot --nginx -d myapp.com -d www.myapp.com
# certbot会自动修改Nginx配置,添加SSL块
sudo systemctl reload nginx
步骤7:防火墙配置(UFW)
Ubuntu 18.04默认用UFW:
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full' # 开放80和443
sudo ufw enable
sudo ufw status verbose
步骤8:最终验证
# 检查PM2状态
pm2 status
# 检查systemd状态
sudo systemctl status pm2-deploy
# 检查Nginx状态
sudo systemctl status nginx
# 查看实时日志
pm2 logs myapp --raw | grep -E "(GET|POST|ERROR)"
sudo tail -f /var/log/nginx/myapp_access.log
# 用curl测试
curl -I https://myapp.com
curl -I https://myapp.com/api/health
4.2 关键参数计算与调优实证
Node.js进程数(-i 参数)怎么定?
不是越多越好。Ubuntu 18.04的 ulimit -u (用户最大进程数)默认384。每个Node实例本身会开多个线程(V8主线程 + libuv线程池),一个实例平均占20-30个进程。所以 -i 4 会占用80-120个进程,留足余量给系统和其他服务。实测数据:在4核8GB的VPS上, -i 4 比 -i 8 吞吐量高12%,因为 -i 8 导致CPU上下文切换过多, %sys 时间飙升。
Nginx worker_processes 怎么设? /etc/nginx/nginx.conf 里:
worker_processes auto; # 自动匹配CPU核心数
worker_rlimit_nofile 65536; # 提升每个worker的文件描述符上限
events {
worker_connections 4096; # 每个worker最多4096连接
use epoll; # Ubuntu 18.04用epoll比select高效
}
worker_rlimit_nofile 必须设,否则 worker_connections 无效。计算总连接数: worker_processes × worker_connections 。 auto 在4核机器上是4, 4×4096=16384 ,足够支撑万级并发。
PM2内存阈值(--max-memory-restart)怎么算?
用 pm2 monit 观察24小时:
- 记录
Heap Total和Heap Used峰值(比如Heap Used峰值是420MB) - 计算
RSS(Resident Set Size):ps aux | grep node | awk '{print $6}',取最大值(比如510MB) - 设阈值为
RSS峰值 × 1.2,即612M,向上取整为640M - 为什么用RSS不是Heap Used?因为
Heap Used是V8堆内存,RSS是进程实际占用的物理内存,包括C++模块、文件缓存等。OOM Killer杀的是RSS超限的进程。
Nginx超时时间(proxy_read_timeout)怎么定?
Node.js的 app.set('trust proxy', 1) 启用后, req.ip 取自 X-Forwarded-For ,但 proxy_read_timeout 决定Nginx等Node多久。如果Node有耗时SQL查询(比如报表导出),设太短会504。实测: proxy_read_timeout 300s (5分钟)足够覆盖99%的业务场景,再长就该优化SQL了。 proxy_connect_timeout 5s 必须短,因为这是Nginx连Node的TCP握手时间,超时说明Node进程挂了。
4.3 生产环境必备监控与告警
部署完不等于结束。Ubuntu 18.04生产环境必须有三层监控:
第一层:基础设施监控(systemd + UptimeRobot)
# 检查PM2服务是否存活
sudo systemctl is-active --quiet pm2-deploy &&更多推荐
所有评论(0)