Nginx 是生产 Web 服务器,可处理几乎所有高流量 Web 部署。如果配置得当,它可以提供 SSL 终止、高效的静态文件服务、WebSocket 代理、速率限制和安全标头 — 所有这些都在单个请求到达您的 Node.js 应用程序之前完成。如果配置不当,它就会成为瓶颈、安全责任或神秘的 Cloudflare 重定向循环的原因。
属于我们的性能与可扩展性系列
阅读完整指南Nginx 是生产 Web 服务器,可处理几乎所有高流量 Web 部署。如果配置得当,它可以提供 SSL 终止、高效的静态文件服务、WebSocket 代理、速率限制和安全标头 — 所有这些都在单个请求到达您的 Node.js 应用程序之前完成。如果配置不当,它就会成为瓶颈、安全责任或神秘的 Cloudflare 重定向循环的原因。
本指南涵盖了多应用 Node.js 部署的生产级 Nginx 配置:Next.js 前端、NestJS API 和 Docusaurus 文档 - 所有这些都在 Cloudflare 后面的同一服务器上运行。
要点
- 切勿将 www 和非 www 拆分为 Cloudflare 后面的单独服务器块 — 导致重定向循环
- 在
/etc/nginx/conf.d/中使用单个 Nginx 配置文件 — 不要在sites-enabled/中也使用符号链接X-XSS-Protection已弃用 — 使用 CSP 代替;X-Frame-Options: DENY仍然有效- 使用路径前缀时
proxy_pass必须包含尾部斜杠- 文本/* MIME 类型的 Gzip 压缩始终值得启用
- 速率限制需要在
http{}级别定义limit_req_zone,而不是server{}级别- Let's Encrypt 证书使用 Certbot 自动续订;添加一个 cron 作业以在续订后重新加载 Nginx
- WebSocket 代理需要特定标头:
Upgrade和Connection
目录结构
保持 Nginx 配置井井有条:
/etc/nginx/
nginx.conf — Main config (rarely touch this)
conf.d/
ecosire-production.conf — All your server blocks in ONE file
snippets/
ssl-params.snippet — SSL hardening (included by server blocks)
proxy-params.snippet — Common proxy headers
security-headers.snippet — Security headers
关键:将所有配置放入 conf.d/ecosire-production.conf 中。也不要将其添加到 sites-enabled/ — 这会导致 Nginx 处理配置两次,从而导致重复的 limit_req_zone 错误和意外行为。
速率限制区域
在配置中的 http{} 级别定义速率限制区域。它们不能在 server{} 块内定义:
# /etc/nginx/conf.d/ecosire-production.conf
# Must be at http{} level — these are in the main conf or conf.d root
limit_req_zone $binary_remote_addr zone=api_general:10m rate=60r/m;
limit_req_zone $binary_remote_addr zone=api_auth:10m rate=10r/m;
limit_req_zone $binary_remote_addr zone=api_public:10m rate=30r/m;
limit_req_zone $binary_remote_addr zone=static_files:10m rate=200r/m;
主应用程序服务器块
# Main web application — Next.js on port 3000
server {
listen 80;
listen [::]:80;
server_name ecosire.com www.ecosire.com;
# Cloudflare handles SSL termination — Nginx only sees HTTP
# (If direct SSL, add listen 443 ssl and certificate paths)
# Security headers
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
# CSP — adjust based on your needs
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' https://js.stripe.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; frame-src https://js.stripe.com; connect-src 'self' https://api.ecosire.com;" always;
# Remove server version from responses
server_tokens off;
# Gzip compression
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types
text/plain
text/css
text/javascript
application/javascript
application/json
application/xml
image/svg+xml
font/woff2;
gzip_min_length 1024;
# ─── Static Files: Next.js build output ─────────────────────────
location /_next/static/ {
proxy_pass http://127.0.0.1:3000;
add_header Cache-Control "public, max-age=31536000, immutable";
# Files include content hash in filename — safe to cache forever
}
# ─── Public static assets ────────────────────────────────────────
location /assets/ {
proxy_pass http://127.0.0.1:3000;
add_header Cache-Control "public, max-age=86400"; # 1 day
}
# ─── Well-known files (no locale prefix) ─────────────────────────
location /.well-known/ {
proxy_pass http://127.0.0.1:3000;
add_header Cache-Control "public, max-age=86400";
}
# ─── App routes (rate limited) ───────────────────────────────────
location / {
limit_req zone=api_general burst=20 nodelay;
limit_req_status 429;
proxy_pass http://127.0.0.1:3000;
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_cache_bypass $http_upgrade;
# Timeouts
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
}
API 服务器块
# NestJS API — port 3001
server {
listen 80;
listen [::]:80;
server_name api.ecosire.com;
server_tokens off;
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# CORS — handled by NestJS, but Nginx can add preflight response
# for performance (avoids reaching Node.js for OPTIONS)
location / {
if ($request_method = 'OPTIONS') {
add_header Access-Control-Allow-Origin "https://ecosire.com";
add_header Access-Control-Allow-Methods "GET, POST, PUT, PATCH, DELETE, OPTIONS";
add_header Access-Control-Allow-Headers "Content-Type, Authorization";
add_header Access-Control-Allow-Credentials "true";
add_header Access-Control-Max-Age 1728000;
add_header Content-Length 0;
return 204;
}
limit_req zone=api_general burst=30 nodelay;
proxy_pass http://127.0.0.1:3001;
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_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
# Stricter rate limits for auth endpoints
location ~ ^/api/auth/(login|exchange|callback) {
limit_req zone=api_auth burst=5 nodelay;
limit_req_status 429;
proxy_pass http://127.0.0.1:3001;
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;
}
# Health check — no rate limiting
location /api/health {
proxy_pass http://127.0.0.1:3001;
proxy_set_header Host $host;
access_log off; # Don't log health check spam
}
# Stripe webhook — needs raw body
location /api/billing/webhook {
proxy_pass http://127.0.0.1:3001;
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;
# No rate limiting on webhooks — Stripe IPs are trusted
# NestJS validates the Stripe signature
}
}
WebSocket 代理
如果您的应用程序使用 WebSockets(Socket.IO、NestJS WebSocket 网关),则代理配置需要特定标头:
# WebSocket upgrade handling
location /ws/ {
proxy_pass http://127.0.0.1:3001;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade"; # Capital U required
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
# WebSocket connections can be long-lived
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
如果没有 Upgrade 和 Connection 标头,浏览器的 WebSocket 握手将失败并出现 426 Upgrade Required 或 101 Switching Protocols 错误。
直接 SSL(无需 Cloudflare)
如果不使用 Cloudflare,请直接在 Nginx 中处理 SSL 终止:
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name ecosire.com;
ssl_certificate /etc/letsencrypt/live/ecosire.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/ecosire.com/privkey.pem;
# SSL hardening
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
# ... rest of server block
}
# HTTP to HTTPS redirect
server {
listen 80;
listen [::]:80;
server_name ecosire.com www.ecosire.com;
return 301 https://ecosire.com$request_uri;
}
Cloudflare 特定配置
在 Cloudflare 后面,Nginx 只能看到 Cloudflare 的 IP。要保留真实的客户端 IP:
# Real IP from Cloudflare — add this in http{} or at top of server{}
set_real_ip_from 103.21.244.0/22;
set_real_ip_from 103.22.200.0/22;
set_real_ip_from 103.31.4.0/22;
set_real_ip_from 104.16.0.0/13;
set_real_ip_from 104.24.0.0/14;
set_real_ip_from 108.162.192.0/18;
set_real_ip_from 131.0.72.0/22;
set_real_ip_from 141.101.64.0/18;
set_real_ip_from 162.158.0.0/15;
set_real_ip_from 172.64.0.0/13;
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 188.114.96.0/20;
set_real_ip_from 190.93.240.0/20;
set_real_ip_from 197.234.240.0/22;
set_real_ip_from 198.41.128.0/17;
set_real_ip_from 2400:cb00::/32;
set_real_ip_from 2606:4700::/32;
set_real_ip_from 2803:f800::/32;
set_real_ip_from 2405:b500::/32;
set_real_ip_from 2405:8100::/32;
set_real_ip_from 2a06:98c0::/29;
set_real_ip_from 2c0f:f248::/32;
real_ip_header CF-Connecting-IP;
另外:在使用 Cloudflare 时,永远不要将 www.ecosire.com 和 ecosire.com 拆分为单独的 Nginx 服务器块。 Cloudflare 在其边缘处理 www 重定向。如果您添加重定向到非 www 的 www 服务器块,并且 Cloudflare 也重定向 www,则会出现重定向循环 (ERR_TOO_MANY_REDIRECTS)。
日志配置
# Custom log format with useful fields
log_format json_combined escape=json
'{'
'"time":"$time_iso8601",'
'"remote_addr":"$remote_addr",'
'"cf_ip":"$http_cf_connecting_ip",'
'"method":"$request_method",'
'"uri":"$request_uri",'
'"status":$status,'
'"body_bytes":$body_bytes_sent,'
'"response_time":$request_time,'
'"referrer":"$http_referer",'
'"user_agent":"$http_user_agent"'
'}';
access_log /var/log/nginx/ecosire-access.log json_combined;
error_log /var/log/nginx/ecosire-error.log warn;
测试您的配置
# Test syntax before applying
nginx -t
# Reload without downtime
nginx -s reload
# Check which config file is active
nginx -T | grep "configuration file"
# Test a specific server block
curl -I https://ecosire.com
curl -I https://api.ecosire.com/api/health
# Check security headers
curl -I https://ecosire.com | grep -E "X-Frame|X-Content|Referrer|Content-Security"
常见问题
我应该在新项目中使用 Nginx 还是 Caddy?
Caddy 的配置要简单得多——它自动处理 Let's Encrypt SSL,并且具有开箱即用的合理默认值。 Nginx 更强大,并且拥有更大的模块和文档生态系统。对于大多数新项目来说,Caddy 是更好的起点;如果您需要对 SSL、复杂的上游路由或 Nginx 特定模块进行细粒度控制,请切换到 Nginx。对于已安装 Nginx 的现有 Linux 服务器,请坚持使用 Nginx。
如何避免重复的 limit_req_zone 错误?
limit_req_zone 指令必须出现在 http{} 上下文级别,而不是出现在 server{} 块内。在包含多个配置文件的 Turborepo monorepo 部署中,请确保该指令在所有包含的文件中仅出现一次。如果您看到错误,请检查是否有 conf.d/app.conf 和 sites-enabled/ 符号链接指向同一文件。
如何为 Next.js ISR(增量静态再生)配置 Nginx?
Next.js 在内部处理 ISR——Nginx 只需要将所有请求代理到 Next.js,而不缓存响应。不要添加干扰 Next.js 的 ISR 缓存标头的 Cache-Control 标头。对于 /_next/static/ 中的静态资源,请添加 immutable 缓存标头,因为这些文件具有内容哈希名称。对于所有其他路由,让 Next.js 设置缓存标头。
启用 Cloudflare 后,为什么我的 SSL 分数会下降?
当 Cloudflare 处于代理模式时,SSL 实验室会测试 Cloudflare 的 SSL,而不是您的 SSL。将 Cloudflare 的 SSL 模式设置为“完全(严格)”以确保 Cloudflare 验证您的原始证书。在您的服务器上安装 Cloudflare Origin 证书 — 它免费且有效期为 15 年。您的 SSL Labs 分数将成为 Cloudflare 的分数(通常是 A+),这实际上是对典型的自配置 Nginx SSL 设置的改进。
如何在同一服务器上处理多个 Node.js 应用程序?
在不同的端口上运行每个应用程序(Next.js 在 3000 上运行,NestJS 在 3001 上运行,Docusaurus 在 3002 上运行等),并在 Nginx 配置中为每个子域添加一个服务器块。使用 PM2 管理所有 Node.js 进程。 Nginx 按子域(或路径前缀)路由到正确的端口。每个服务器块都可以有自己的速率限制、缓存和安全标头配置。
后续步骤
生产 Nginx 配置是一个动态文档 - 它随着应用程序需求的变化、流量模式的变化以及新的安全最佳实践的出现而不断发展。 ECOSIRE 在生产环境中运行 Nginx,为多个应用程序提供服务,处理 SSL 终止、速率限制以及跨所有域的 Cloudflare 集成。
无论您需要 DevOps 咨询、生产基础设施设置还是完整的部署架构设计,探索我们的服务 以了解我们如何提供帮助。
作者
ECOSIRE Research and Development Team技术写作
ECOSIRE 技术写作团队的内容涵盖 Odoo ERP、Shopify 电子商务、AI 智能体、Power BI 分析、GoHighLevel 自动化以及企业软件最佳实践。我们的指南帮助企业做出明智的技术决策。
相关文章
电子商务人工智能欺诈检测:在不阻止销售的情况下保护收入
实施 AI 欺诈检测,捕获 95% 以上的欺诈交易,同时将误报率控制在 2% 以下。机器学习评分、行为分析和投资回报率指南。
API 速率限制:模式和最佳实践
通过令牌桶、滑动窗口和固定计数器模式掌握 API 速率限制。使用 NestJS 节流器、Redis 和实际配置示例保护您的后端。
2026-2027 年网络安全趋势:零信任、人工智能威胁和防御
2026-2027 年网络安全趋势的权威指南——人工智能驱动的攻击、零信任实施、供应链安全和构建弹性安全计划。
更多来自性能与可扩展性
Webhook 调试和监控:完整的故障排除指南
通过这份涵盖故障模式、调试工具、重试策略、监控仪表板和安全最佳实践的完整指南掌握 Webhook 调试。
k6 负载测试:在发布之前对您的 API 进行压力测试
掌握 Node.js API 的 k6 负载测试。涵盖虚拟用户启动、阈值、场景、HTTP/2、WebSocket 测试、Grafana 仪表板和 CI 集成模式。
Odoo 性能调优:PostgreSQL 和服务器优化
Odoo 19 性能调优专家指南。涵盖 PostgreSQL 配置、索引、查询优化、Nginx 缓存和企业部署的服务器大小调整。
在生产中测试和监控 AI 代理
在生产环境中测试和监控 AI 代理的完整指南。涵盖 OpenClaw 部署的评估框架、可观测性、漂移检测和事件响应。
使用 OpenClaw 进行合规性监控代理
部署 OpenClaw AI 代理以进行持续合规性监控。自动执行监管检查、策略执行、审计跟踪生成和合规性报告。
优化 AI 代理成本:令牌使用和缓存
通过令牌优化、缓存、模型路由和使用情况监控来降低 AI 代理运营成本的实用策略。生产 OpenClaw 部署真正节省了成本。