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 &&

更多推荐