引言:为什么需要反向代理

现代 Web 服务几乎很少由单台服务器独立运营。前端通常使用 React 或 Vue 构建为静态文件进行分发,后端则由 Node.js、Python、Java、Go 等编写的应用服务器负责。再加上数据库、缓存、消息队列,整个服务就会成为由数十个组件组成的复杂系统。

此时,Nginx 反向代理就在所有这些组件前端扮演着流量指挥者的角色。用户仅通过 https://example.com 一个入口访问,但 Nginx 会根据请求路径和主机名将流量路由到合适的后端。由此,SSL 终止、负载均衡、缓存、安全、压缩、监控等都可以在中心统一管理。

本指南将涵盖从 Nginx 反向代理的概念到实战配置、优化、问题排查等实际工作中所需的全部内容。我们将通过 Node.js、Python、Java 等多种后端对接示例,以及 WebSocket、gRPC 等特殊协议的处理方式,并结合实际配置文件详细讲解运营中常遇到的问题的解决方案。

1. 反向代理 vs 正向代理

1.1 概念区别

正向代理(Forward Proxy)位于客户端一侧,代替客户端向外部服务器发起请求。公司的防火墙、VPN、学校的互联网过滤等都是典型示例。客户端知道代理的存在,但服务器并不知道代理的存在。

反向代理(Reverse Proxy)位于服务器一侧,代替服务器接收客户端请求。客户端会把代理视为真正的服务器,并不知道隐藏在代理后面的真实服务器(后端)的存在。

分类 正向代理 反向代理
位置 客户端一侧 服务器一侧
代理对象 代理客户端 代理服务器
主要用途 访问控制、缓存、匿名化 负载均衡、SSL 终止、缓存
代表示例 Squid、Privoxy Nginx、HAProxy、Traefik

1.2 反向代理的主要优势

  • 单一入口:将多个后端服务器整合到一个域名/IP 下
  • SSL 终止:在代理处集中管理 HTTPS 处理,后端可使用 HTTP
  • 负载均衡:将流量分散到多个后端
  • 缓存:减少后端负载,提高响应速度
  • 压缩:在代理处处理 Gzip/Brotli 压缩
  • 安全性:防止后端服务器直接暴露,可应用 WAF
  • 速率限制:在中心统一管理请求限制
  • 日志集中化:所有请求在一处进行日志记录

2. proxy_pass 基本用法

2.1 最基础的代理配置

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://127.0.0.1:3000;
    }
}

仅凭这样就能实现基本的反向代理。但在实际工作中,至少必须设置以下头信息:

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://127.0.0.1:3000;

        # 传递原始主机信息
        proxy_set_header Host $host;

        # 客户端真实 IP(后端记录日志时使用)
        proxy_set_header X-Real-IP $remote_addr;

        # 代理链信息
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

        # 原始协议 (http/https)
        proxy_set_header X-Forwarded-Proto $scheme;

        # 原始端口
        proxy_set_header X-Forwarded-Port $server_port;

        # 原始主机
        proxy_set_header X-Forwarded-Host $host;
    }
}
重要:如果不设置 proxy_set_header Host $host;,Nginx 会默认将 proxy_pass 中指定的主机(这里是 127.0.0.1:3000)作为 Host 头传递。这会在使用虚拟主机路由的后端引发问题。

2.2 proxy_pass URL 中斜杠的区别

proxy_pass URL 末尾是否带有斜杠(/),行为会完全不同。这是 Nginx 初学者最容易混淆的部分:

# 情况 1:proxy_pass 无斜杠
# /api/users → http://backend/api/users (原样传递路径)
location /api/ {
    proxy_pass http://backend;
}

# 情况 2:proxy_pass 带斜杠
# /api/users → http://backend/users (移除 location 路径)
location /api/ {
    proxy_pass http://backend/;
}

# 情况 3:路径替换
# /api/users → http://backend/v1/users (/api/ 被替换为 /v1/)
location /api/ {
    proxy_pass http://backend/v1/;
}
记忆要点:
无斜杠 = 原样传递路径
带斜杠 = 截断 location 路径,仅传递剩余部分
如果不能明确理解这个差异,就会出现 404 错误或错误的路由。

3. 各后端的代理配置

3.1 Node.js (Express、Next.js)

# Node.js 应用(默认端口 3000)
upstream nodejs_backend {
    server 127.0.0.1:3000;
    keepalive 32;
}

server {
    listen 80;
    server_name app.example.com;

    location / {
        proxy_pass http://nodejs_backend;
        proxy_http_version 1.1;
        proxy_set_header Connection "";    # 使用 keepalive

        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;

        # 为了 Node.js 的 req.ip,需要 trust proxy 设置
        # app.set('trust proxy', true);

        proxy_read_timeout 300;
        proxy_connect_timeout 75;
    }
}

Next.js 特化配置

server {
    listen 80;
    server_name next.example.com;

    # Next.js 静态文件(构建时生成)
    location /_next/static/ {
        proxy_pass http://127.0.0.1:3000;
        proxy_cache_valid 200 60m;
        add_header Cache-Control "public, max-age=31536000, immutable";
    }

    # 图像优化 API
    location /_next/image {
        proxy_pass http://127.0.0.1:3000;
        proxy_cache_valid 200 60m;
    }

    # 其余请求
    location / {
        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_cache_bypass $http_upgrade;
    }
}

3.2 Python (Django、Flask、FastAPI)

# 使用 Gunicorn 运行的 Django/Flask/FastAPI
upstream python_backend {
    server 127.0.0.1:8000 fail_timeout=30s;
}

server {
    listen 80;
    server_name py.example.com;

    # 静态文件由 Nginx 直接提供(Django collectstatic 等)
    location /static/ {
        alias /var/www/example/static/;
        expires 30d;
        access_log off;
    }

    location /media/ {
        alias /var/www/example/media/;
        expires 7d;
    }

    location / {
        proxy_pass http://python_backend;
        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;

        # Python 应用响应时间通常可能较长
        proxy_read_timeout 120;
        proxy_connect_timeout 75;

        # Django: SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
        # Flask: app.wsgi_app = ProxyFix(app.wsgi_app, x_proto=1, x_host=1)
    }
}

使用 Unix 套接字(性能提升)

# Gunicorn 以 Unix 套接字方式运行的情况
# gunicorn app:app --bind unix:/tmp/gunicorn.sock
upstream python_backend {
    server unix:/tmp/gunicorn.sock;
}

server {
    listen 80;
    server_name py.example.com;

    location / {
        proxy_pass http://python_backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

3.3 Java (Spring Boot、Tomcat)

# Spring Boot 应用(默认端口 8080)
upstream spring_backend {
    server 127.0.0.1:8080;
    keepalive 32;
}

server {
    listen 80;
    server_name java.example.com;

    client_max_body_size 50M;    # Java 应用经常有较大的载荷

    location / {
        proxy_pass http://spring_backend;
        proxy_http_version 1.1;
        proxy_set_header Connection "";

        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;

        # Spring Boot application.properties:
        # server.forward-headers-strategy=native
        # server.tomcat.remote-ip-header=X-Forwarded-For
        # server.tomcat.protocol-header=X-Forwarded-Proto

        proxy_read_timeout 300;
        proxy_buffer_size 16k;
        proxy_buffers 8 16k;
    }
}

3.4 PHP-FPM(单独章节)

PHP 使用 fastcgi_pass 而非 proxy_pass。严格来说这不是反向代理,而是 FastCGI 代理:

server {
    listen 80;
    server_name php.example.com;
    root /var/www/php-app/public;
    index index.php;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
}

4. WebSocket 代理

WebSocket 使用 HTTP 1.1 的 Upgrade 机制。使用一般的 HTTP 代理配置,WebSocket 将无法正常工作,需要如下特殊配置:

# WebSocket 代理基本配置
server {
    listen 80;
    server_name ws.example.com;

    location / {
        proxy_pass http://127.0.0.1:8080;

        # WebSocket 必需设置
        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;

        # WebSocket 保持长时间连接
        proxy_read_timeout 86400s;    # 24 小时
        proxy_send_timeout 86400s;

        # 禁用缓冲(实时双向通信)
        proxy_buffering off;
    }
}

4.1 HTTP 和 WebSocket 同时处理

# 如同 Socket.IO 一样在同一端口处理 HTTP 和 WebSocket 的情况
# $connection_upgrade 映射定义 (http 块)
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

server {
    listen 80;
    server_name chat.example.com;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_read_timeout 86400s;
    }

    # 仅将 WebSocket 分离到单独路径的情况
    location /socket.io/ {
        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;
    }
}

5. 微服务路由

将多个后端服务整合到单一域名,是反向代理最强大的应用场景。

5.1 基于路径的路由

# upstream 定义
upstream frontend { server 127.0.0.1:3000; }
upstream api_service { server 127.0.0.1:4000; }
upstream auth_service { server 127.0.0.1:5000; }
upstream upload_service { server 127.0.0.1:6000; }

server {
    listen 80;
    server_name example.com;

    # 前端 (SPA)
    location / {
        proxy_pass http://frontend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }

    # API 服务
    location /api/ {
        proxy_pass http://api_service/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    # 认证服务
    location /auth/ {
        proxy_pass http://auth_service/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;

        # 认证服务的速率限制应更严格
        limit_req zone=auth_limit burst=5 nodelay;
    }

    # 上传服务(处理大文件)
    location /upload/ {
        proxy_pass http://upload_service/;
        proxy_set_header Host $host;

        # 上传允许大 body
        client_max_body_size 500M;

        # 设置较长的超时时间
        proxy_read_timeout 300s;
        proxy_send_timeout 300s;

        # 禁用缓冲(流式上传)
        proxy_request_buffering off;
    }
}

5.2 基于主机名的路由(子域名)

# 将各子域名路由到不同的服务
server {
    listen 80;
    server_name app.example.com;
    location / { proxy_pass http://127.0.0.1:3000; proxy_set_header Host $host; }
}

server {
    listen 80;
    server_name api.example.com;
    location / { proxy_pass http://127.0.0.1:4000; proxy_set_header Host $host; }
}

server {
    listen 80;
    server_name admin.example.com;
    location / {
        allow 10.0.0.0/8;
        deny all;
        proxy_pass http://127.0.0.1:5000;
        proxy_set_header Host $host;
    }
}

6. 负载均衡集成

当运行多个后端实例时,可以在反向代理中自然地实现负载均衡。

# 多个后端实例
upstream api_cluster {
    # 负载均衡方式(未指定时为 round-robin)
    least_conn;

    server 10.0.0.10:8080 weight=3 max_fails=3 fail_timeout=30s;
    server 10.0.0.11:8080 weight=2 max_fails=3 fail_timeout=30s;
    server 10.0.0.12:8080 weight=1 max_fails=3 fail_timeout=30s;

    server 10.0.0.13:8080 backup;   # 备份服务器
    server 10.0.0.14:8080 down;     # 维护中

    keepalive 32;    # 每个 worker 保持的空闲连接数
}

server {
    listen 80;
    server_name api.example.com;

    location / {
        proxy_pass http://api_cluster;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        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_next_upstream error timeout http_500 http_502 http_503 http_504;
        proxy_next_upstream_tries 3;
        proxy_next_upstream_timeout 10s;
    }
}

7. 代理缓存

利用反向代理的缓存功能,可以大幅降低后端的负载。

# 在 http 块中定义缓存区域
http {
    proxy_cache_path /var/cache/nginx/api
                     levels=1:2
                     keys_zone=api_cache:10m
                     max_size=1g
                     inactive=60m
                     use_temp_path=off;

    server {
        listen 80;
        server_name api.example.com;

        location / {
            proxy_pass http://api_backend;
            proxy_set_header Host $host;

            # 启用缓存
            proxy_cache api_cache;
            proxy_cache_key "$scheme$request_method$host$request_uri";

            # 缓存有效时间
            proxy_cache_valid 200 302 10m;   # 成功响应缓存 10 分钟
            proxy_cache_valid 404 1m;        # 404 缓存 1 分钟
            proxy_cache_valid any 30s;       # 其他缓存 30 秒

            # 缓存锁(并发请求时仅调用一次后端)
            proxy_cache_lock on;
            proxy_cache_lock_timeout 5s;

            # 缓存使用条件
            proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
            proxy_cache_background_update on;

            # 缓存状态头(用于调试)
            add_header X-Cache-Status $upstream_cache_status;

            # 缓存跳过条件
            proxy_cache_bypass $cookie_nocache $arg_nocache;
            proxy_no_cache $cookie_nocache $arg_nocache;
        }
    }
}
缓存状态确认:通过 X-Cache-Status 头的值可以确认缓存的工作情况。
HIT - 从缓存响应
MISS - 缓存中没有,调用后端
EXPIRED - 缓存已过期
BYPASS - 缓存跳过条件
UPDATING - 缓存更新中(返回 stale 响应)

8. 缓冲与超时调优

8.1 理解缓冲

Nginx 默认会对后端响应进行缓冲。这种方式将后端的响应收集到内存(或磁盘)后再传递给客户端。这在防止因慢速客户端而长时间占用后端连接方面起着重要作用。

location / {
    proxy_pass http://backend;

    # 响应缓冲设置
    proxy_buffering on;              # 默认值
    proxy_buffer_size 4k;            # 响应头缓冲区
    proxy_buffers 8 4k;              # 响应体缓冲区(数量 x 大小)
    proxy_busy_buffers_size 8k;      # 正在发送到客户端的缓冲区
    proxy_max_temp_file_size 1024m;  # 临时文件最大大小
    proxy_temp_file_write_size 8k;

    # 请求缓冲
    proxy_request_buffering on;      # 默认值
    client_body_buffer_size 16k;
    client_max_body_size 10m;
}

8.2 流式响应(禁用缓冲)

# Server-Sent Events (SSE)、流式 API、大文件下载
location /stream/ {
    proxy_pass http://backend;

    # 禁用缓冲
    proxy_buffering off;
    proxy_request_buffering off;

    # 支持分块传输
    proxy_http_version 1.1;
    proxy_set_header Connection "";

    # 立即传递
    proxy_cache off;

    # 较长的超时时间
    proxy_read_timeout 24h;
}

8.3 超时设置

location / {
    proxy_pass http://backend;

    # 后端连接超时 (TCP 握手)
    proxy_connect_timeout 60s;

    # 向后端发送请求的超时
    proxy_send_timeout 60s;

    # 等待后端响应的超时
    proxy_read_timeout 60s;
}

9. 安全头与客户端保护

server {
    listen 443 ssl http2;
    server_name example.com;

    # 安全头(应用于所有 location)
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
    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 Content-Security-Policy "default-src 'self'" always;
    add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;

    location / {
        proxy_pass http://backend;
        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_hide_header X-Powered-By;
        proxy_hide_header X-AspNet-Version;
        proxy_hide_header Server;
    }
}

9.1 速率限制与连接数限制

# 在 http 块中定义 zone
http {
    # 每个 IP 每秒请求数限制
    limit_req_zone $binary_remote_addr zone=general:10m rate=10r/s;
    limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;
    limit_req_zone $binary_remote_addr zone=api:10m rate=30r/s;

    # 每个 IP 的并发连接数限制
    limit_conn_zone $binary_remote_addr zone=conn_limit:10m;

    server {
        location / {
            limit_req zone=general burst=20 nodelay;
            limit_conn conn_limit 10;
            proxy_pass http://backend;
        }

        location /login {
            limit_req zone=login burst=3 nodelay;
            proxy_pass http://auth_backend;
        }

        location /api/ {
            limit_req zone=api burst=50 nodelay;
            proxy_pass http://api_backend;
        }
    }
}

10. 实战问题排查

10.1 504 Gateway Timeout

原因:后端响应超过了 proxy_read_timeout(默认 60 秒)
解决方法:

location / {
    proxy_pass http://backend;
    proxy_connect_timeout 75s;
    proxy_send_timeout 300s;
    proxy_read_timeout 300s;   # 根据需要增加
}

10.2 502 Bad Gateway

原因:后端服务器宕机或无法访问
解决方法:

# 检查后端进程
ps aux | grep node    # 或 python、java 等

# 检查端口
ss -tlnp | grep 3000

# 查看 Nginx 错误日志
tail -f /var/log/nginx/error.log | grep upstream

# 直接调用后端服务器进行测试
curl -v http://127.0.0.1:3000/

10.3 413 Request Entity Too Large

原因:上传的文件大小超过了 client_max_body_size(默认 1MB)
解决方法:

# 全局设置 (http 块)
client_max_body_size 100M;

# 或仅针对特定 location
location /upload/ {
    client_max_body_size 500M;
    proxy_pass http://upload_backend;
}

10.4 后端应用重定向为 HTTP

现象:以 HTTPS 连接,但后端重定向后变为 HTTP
原因:后端不知道原始协议
解决方法:

location / {
    proxy_pass http://backend;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-Host $host;

    # 或在 Nginx 层重写重定向
    proxy_redirect http:// https://;
}
各框架的设置:
Express:app.set('trust proxy', true)
Django:SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
Flask:ProxyFix(app.wsgi_app, x_proto=1)
Spring Boot:server.forward-headers-strategy=native

10.5 CORS 预检请求问题

location /api/ {
    # 处理 CORS 预检请求
    if ($request_method = 'OPTIONS') {
        add_header 'Access-Control-Allow-Origin' '$http_origin' always;
        add_header 'Access-Control-Allow-Credentials' 'true' always;
        add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
        add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, Accept' always;
        add_header 'Access-Control-Max-Age' 1728000 always;
        add_header 'Content-Type' 'text/plain; charset=utf-8' always;
        add_header 'Content-Length' 0 always;
        return 204;
    }

    # 实际请求
    add_header 'Access-Control-Allow-Origin' '$http_origin' always;
    add_header 'Access-Control-Allow-Credentials' 'true' always;

    proxy_pass http://api_backend;
    proxy_set_header Host $host;
}

11. 反向代理运维检查清单

  • 代理头传递:设置 Host、X-Real-IP、X-Forwarded-For、X-Forwarded-Proto
  • 后端框架设置:通过 trust proxy 相关配置识别原始 IP/协议
  • 合适的超时设置:根据服务特性设置读取/连接/发送超时
  • 上传大小限制:client_max_body_size 的适当值
  • WebSocket 支持:根据需要设置 Upgrade 头
  • 负载均衡故障处理:max_fails、fail_timeout、备份服务器
  • 安全头:应用 HSTS、X-Frame-Options 等
  • 速率限制:在敏感路径(登录、API)应用限制
  • 日志分离:按服务分离代理日志与错误日志
  • 监控:收集 upstream_response_time 日志
  • 后端健康检查:定期的健康检查脚本
  • 配置版本管理:使用 Git 管理配置文件

结论:反向代理是现代 Web 基础设施的基本功

Nginx 反向代理已超越了简单的"请求转发"功能,成为现代 Web 基础设施的核心组件。它可以将 SSL 终止、负载均衡、缓存、安全、监控等运维所需的几乎所有功能集中到一处,让后端应用程序能够专注于业务逻辑。

本指南涉及的核心要点:

  • proxy_set_header 必不可少 - 务必设置 Host、X-Real-IP、X-Forwarded-* 头。
  • proxy_pass 的斜杠 - 因为路径行为会完全不同,必须准确理解。
  • 考虑各后端的特性 - Node.js 的 keepalive、Python 的 Unix 套接字、Java 的大缓冲区等,需要根据各自特性进行相应的配置。
  • WebSocket 需要 Upgrade 头 - 普通的 HTTP 代理配置无法正常工作。
  • 谨慎设置超时 - 过短会导致正常请求失败,过长会浪费资源。
  • 用缓存减轻后端负担 - 大部分 GET 请求都可作为缓存目标。
  • 一并考虑安全 - 代理是安全边界,务必设置速率限制和安全头。

请根据自己的服务环境调整并应用本指南中的配置示例。反向代理的配置会随着实战经验的积累而愈加精细,由此可以构建稳定且可扩展的 Web 基础设施。