Nginx 和 PM2 是 Node.js 应用部署中最常见的组合,但默认配置存在大量安全薄弱点。本文从账号隔离、端口控制、密钥管理、网络策略、最小权限、补丁周期和审计日志七个维度,给出可落地的加固方案和决策依据。

软件定制开发团队
"真正有价值的技术内容,应该能帮助客户更快判断方向、预算和落地路径。"
企业选择 Nginx 作为反向代理、PM2 作为进程管理器,通常是因为它们成熟、稳定且文档丰富。但部署上线后,安全配置往往停留在“能跑就行”的状态。Nginx 默认监听 0.0.0.0:80,PM2 默认不限制进程间通信,这些默认行为在生产环境中就是暴露面。
加固不是一次性动作,而是一组持续的策略。本文围绕七个相互关联的维度展开:账号隔离、端口控制、密钥管理、网络策略、最小权限、补丁周期和审计日志。每个维度都有具体的操作步骤和决策条件,避免脱离实际场景的空谈。
生产服务器上最危险的习惯是所有人用 root 或同一个 sudo 用户操作。Nginx 和 PM2 分别运行在不同的系统用户下,是隔离的第一道防线。
大多数发行版安装 Nginx 时会自动创建 `nginx` 或 `www-data` 用户,但很多运维人员为了省事,会修改配置文件让 Nginx 以 root 运行。这是绝对禁止的。
检查当前 Nginx worker 进程的用户:
```
ps aux | grep nginx
```
如果 master 和 worker 都是 root,说明配置有误。正确的做法是在 `nginx.conf` 中设置:
```
user nginx;
```
并确保 `/etc/nginx/` 目录的属主为 root,权限为 750,普通用户无法读取私密配置。
PM2 本身不需要 root 权限。为每个 Node.js 应用创建一个独立的系统用户,例如 `app-user`,然后用该用户启动 PM2。
创建用户的命令示例:
```
sudo useradd -r -s /bin/false app-user
```
`-r` 表示系统用户,`-s /bin/false` 禁止登录 shell。然后切换到此用户启动 PM2:
```
sudo -u app-user pm2 start app.js
```
这样做的好处是:即使应用被攻破,攻击者也无法通过这个用户访问其他系统资源。
如果一台服务器上运行多个 Node.js 应用,每个应用一个独立用户会增加管理成本。此时可以按应用的安全等级分组:面向公网的高风险应用使用独立用户,内部工具或管理后台可以共用用户。决策依据是业务敏感度和暴露面大小。
Nginx 和 PM2 的端口配置是攻击者最直接的入口。
在 `server` 块中明确指定 `listen` 指令。生产环境应避免:
```
listen 80;
```
这种写法会让 Nginx 在所有网络接口上监听。应该改为:
```
listen 192.168.1.10:80;
```
如果服务器有多个 IP,只绑定业务 IP。对于 HTTPS,同样只绑定特定 IP。
对于只接受内网请求的管理后台,绑定内网 IP 即可,不要暴露到公网。
PM2 不直接处理网络端口,但应用本身会监听端口。常见的错误是 Node.js 应用监听 0.0.0.0:3000,然后 Nginx 反向代理到 localhost:3000。这其实多余地暴露了应用端口。
正确做法是:Node.js 应用只监听 127.0.0.1,Nginx 通过 upstream 指向 127.0.0.1:3000。这样外部无法直接访问应用端口,所有流量必须经过 Nginx。
在应用代码中设置:
```javascript
app.listen(3000, '127.0.0.1');
```
如果使用 PM2 的 ecosystem.config.js,可以在 `env` 中指定 `HOST=127.0.0.1`。
部署后使用 `netstat -tlnp` 或 `ss -tlnp` 检查监听地址。如果看到 0.0.0.0:3000 这样的条目,说明需要立即修改。
SSL/TLS 证书私钥、API 密钥、数据库密码——这些敏感信息出现在 Nginx 配置文件或 Node.js 环境变量文件中,是安全审计中最常见的问题。
证书文件(.crt)和私钥文件(.key)的权限要严格区分。证书可以设置为 644,私钥必须为 600 或 400,属主为 root。
示例:
```
sudo chmod 600 /etc/nginx/ssl/example.com.key
sudo chown root:root /etc/nginx/ssl/example.com.key
```
同时确保 Nginx 配置文件中 `ssl_certificate_key` 指向的文件路径不能被非 root 用户读取。
PM2 支持通过 `ecosystem.config.js` 或 `--env` 传递环境变量,但配置文件如果被提交到 Git 仓库,密钥就暴露了。
建议的做法是:
1. 使用 `.env` 文件,并确保 `.gitignore` 包含 `.env`。
2. 在 PM2 启动脚本中读取 `.env`,例如使用 `dotenv` 库。
3. 对 `.env` 文件设置权限 600,属主为应用用户。
更高安全要求的场景,可以使用 HashiCorp Vault 或 AWS Secrets Manager,PM2 通过 API 获取密钥。但这对中小团队来说运维成本较高,需要权衡。
Let's Encrypt 证书每 90 天过期。使用 certbot 时,自动续签脚本会修改 Nginx 配置。务必在续签后执行 `nginx -t` 验证配置,避免因证书文件权限或路径变更导致服务中断。
只靠端口绑定还不够,网络层的访问控制是第二道闸门。
对于只提供 Web 服务的服务器,防火墙规则可以精简为:
以 iptables 为例:
```
iptables -A INPUT -p tcp --dport 22 -s 你的管理IP -j ACCEPT
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -j ACCEPT
iptables -A INPUT -j DROP
```
注意顺序:先放行,最后拒绝所有。
对于管理后台或 API,可以在 Nginx 的 `location` 块中添加 IP 白名单:
```
location /admin {
allow 192.168.1.0/24;
deny all;
proxy_pass http://backend;
}
```
如果使用 Cloudflare 或 AWS CloudFront,Nginx 应只信任这些 CDN 的 IP 段,并通过 `real_ip` 模块获取真实客户端 IP。
PM2 本身不提供网络过滤功能,但可以通过 `--node-args` 限制 Node.js 网络行为。更可靠的方式是在应用代码中实现 IP 白名单逻辑,但这会增加业务复杂度。
简单的做法是:确保 PM2 管理的应用只监听 127.0.0.1,所有外部请求由 Nginx 转发。这样网络策略只需要在 Nginx 层和防火墙层实现,降低了管理负担。
最小权限原则不仅适用于用户,也适用于文件和进程。
Nginx 需要读取静态文件,但不应该能写入。PM2 的应用用户需要读写日志目录和临时文件,但不应该能修改 Nginx 配置。
具体操作:
如果 Nginx 需要绑定 80 或 443 端口,但又不希望以 root 运行,可以使用 Linux capabilities:
```
sudo setcap 'cap_net_bind_service=+ep' /usr/sbin/nginx
```
这样 Nginx worker 进程可以在非 root 用户下绑定特权端口。注意:这需要在每次 Nginx 升级后重新执行。
PM2 不需要绑定特权端口,所以不需要设置 capabilities。如果应用需要绑定 80 端口,应该通过 Nginx 反向代理,而不是直接让 Node.js 监听。
高并发场景下,PM2 进程可能因为文件描述符限制而崩溃。在 systemd service 文件中设置:
```
[Service]
LimitNOFILE=65536
```
同时保证系统级别的 `fs.file-max` 足够大。
安全补丁的时机选择直接影响业务连续性。
Nginx 主版本和分支版本的区别决定了更新频率。主线版(mainline)功能更新快,稳定版(stable)只修复安全漏洞。生产环境建议使用稳定版,并订阅 Nginx 安全公告。
更新时机:
更新前必须执行 `nginx -t` 验证配置,并在测试环境先行部署。
PM2 通过 npm 发布,更新命令为:
```
npm install pm2@latest -g
```
但直接升级到最新版可能引入不兼容的 API 变化。建议在测试环境中运行 `pm2 update` 验证现有进程是否正常工作。
PM2 的补丁更新通常不涉及配置变更,但要注意 Node.js 版本的兼容性。PM2 5.x 要求 Node.js 12 以上,6.x 要求 14 以上。升级 PM2 前先确认 Node.js 版本。
不建议在生产环境开启自动更新。Nginx 或 PM2 的自动更新可能在业务高峰期重启服务,导致中断。正确的做法是:
1. 订阅官方安全公告。
2. 在测试环境验证。
3. 在维护窗口内手动更新。
4. 更新后执行功能测试和负载测试。
没有日志的安全加固等于没有加固。Nginx 和 PM2 的日志配置直接影响事后追溯的能力。
默认的访问日志格式包含足够的信息,但建议自定义格式,加入 `$http_x_forwarded_for`、`$http_user_agent` 和 `$request_time`。
示例:
```
log_format extended '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'"$http_x_forwarded_for" $request_time';
```
错误日志级别设为 `warn` 或 `error`,避免 `info` 级别产生过多无用信息。
日志文件需要轮转。使用 `logrotate` 配置,保留最近 30 天的日志,压缩旧日志。
PM2 默认将 stdout 和 stderr 输出到 `~/.pm2/logs/` 目录。日志文件会无限增长,必须配置轮转。
使用 PM2 的日志轮转模块:
```
pm2 install pm2-logrotate
pm2 set pm2-logrotate:max_size 10M
pm2 set pm2-logrotate:retain 7
```
设置 `max_size` 为 10M,`retain` 为 7,表示每个日志文件最大 10MB,保留最近 7 个文件。
对于关键业务,建议将 PM2 日志发送到集中式日志系统,如 ELK 或 Loki。PM2 的 `--log` 参数可以指定日志文件路径,配合 Filebeat 或 Promtail 采集。
当发生安全事件时,审计日志能回答三个问题:
没有日志,这些信息只能靠猜测。日志保留周期取决于合规要求,一般建议至少 180 天。
以上七个维度不是孤立的,而是相互强化的。一个完整的加固流程可以这样组织:
1. **部署前**:创建专用用户、设置最小权限、配置防火墙。
2. **部署时**:绑定特定 IP、配置密钥权限、启用日志。
3. **运行中**:定期审计配置、检查监听端口、更新补丁。
4. **事件后**:分析日志、调整规则、更新策略。
以 SystemDo 过往的项目经验来看,很多团队在部署后的前三个月安全状况最好,之后因为人员变动或业务压力,配置逐渐退化。定期(例如每季度)执行一次安全审计,检查用户列表、端口监听、密钥权限和日志轮转状态,能有效防止退化。
加固不是一次性的项目,而是贯穿整个服务生命周期的持续过程。
继续了解企业数字化、SEO / GEO、AI 自动化和软件定制开发中的常见问题。