原创

Nginx配置反向代理详解

反向代理是 Nginx 最常见、也是最核心的能力之一。它的作用不是直接处理业务逻辑,而是站在客户端和后端服务之间,接收请求后再转发给真正提供服务的应用,例如 Spring Boot、Node.js、Go、PHP-FPM 或其他 HTTP 服务。

很多线上系统都会把 Nginx 放在最前面,原因很直接:它能统一入口、隐藏后端服务地址、做负载均衡、处理静态资源、终止 HTTPS、控制超时与连接,并且性能稳定。

什么是反向代理

先把几个容易混淆的概念说清楚。

正向代理

正向代理服务于客户端。客户端本来无法直接访问目标服务器,于是先访问代理服务器,再由代理服务器代替客户端去访问目标服务。常见场景是科学上网、公司内网访问控制。

反向代理

反向代理服务于服务端。客户端以为自己访问的是一个统一地址,比如 https://api.example.com,实际上请求先到 Nginx,再由 Nginx 转发到后端真实服务,例如:

  • http://127.0.0.1:8080
  • http://192.168.10.20:9000
  • 多台应用服务器组成的集群

客户端通常感知不到后端真实结构,这就是反向代理的核心特征。

为什么项目里经常使用 Nginx 反向代理

反向代理不是“为了配 Nginx 而配 Nginx”,而是解决真实问题。

统一访问入口

前端、移动端、第三方系统只需要访问一个域名,不需要知道后端服务部署在哪台机器、哪个端口。

例如:

  • 用户访问 https://www.example.com
  • Nginx 转发到 http://127.0.0.1:8080

后端哪怕改端口、迁移机器,外部访问地址也可以不变。

隐藏后端服务

后端应用一般不直接暴露在公网。Nginx 作为入口,外部只看到 Nginx 的地址,后端服务保留在内网或本机,安全性更好。

支持负载均衡

当后端服务不止一台时,Nginx 可以把请求分发给多个节点,提高并发处理能力和可用性。

分离静态资源和动态请求

例如:

  • /static/ 由 Nginx 直接返回静态文件
  • /api/ 转发到 Java 或 Node 服务

这样可以减少应用服务器压力。

支持 HTTPS 终止

浏览器与 Nginx 之间使用 HTTPS,Nginx 再通过 HTTP 转发给内网应用。后端应用不用自己处理证书,部署和维护都更简单。

反向代理最基础的配置

先看一个最小可用示例:

server {
    listen 80;
    server_name example.com;

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

这段配置的含义非常明确:

  • Nginx 监听 80 端口
  • 当请求域名是 example.com 时进入这个 server
  • 所有 / 路径下的请求都转发到 127.0.0.1:8080

例如用户访问:

http://example.com/user/list

最终会被转发到:

http://127.0.0.1:8080/user/list

这是理解 Nginx 反向代理的第一步:location 负责匹配请求,proxy_pass 负责转发请求。

一个完整些的反向代理配置应该怎么写

实际项目里,通常不会只写一行 proxy_pass,而是会补充一组代理头与超时参数。

server {
    listen 80;
    server_name example.com;

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

        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_set_header Host $host

把原始请求里的主机名传给后端。

很多后端应用会依赖 Host 头来:

  • 生成回调地址
  • 识别当前访问域名
  • 做多租户或多站点处理

如果不传,后端拿到的主机名可能不是用户实际访问的域名。

proxy_set_header X-Real-IP $remote_addr

把客户端真实 IP 传给后端。否则后端看到的往往只有代理服务器的 IP。

proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for

记录完整的代理链路 IP。这个头在日志分析、风控、审计、限流中都很常用。

proxy_set_header X-Forwarded-Proto $scheme

告诉后端原始请求是 http 还是 https。这在应用生成跳转地址、回调地址时非常重要。

超时配置

  • proxy_connect_timeout:和后端建立连接的超时时间
  • proxy_send_timeout:向后端发送请求的超时时间
  • proxy_read_timeout:读取后端响应的超时时间

如果不根据业务特点设置,线上容易出现请求长时间挂起或者误判超时。

proxy_pass 结尾带不带斜杠,区别非常大

这是 Nginx 反向代理里最常见的坑之一。

情况一:proxy_pass 后面不带斜杠

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

用户请求:

http://example.com/api/user/info

转发结果:

http://127.0.0.1:8080/api/user/info

也就是说,请求 URI 会原样拼接过去。

情况二:proxy_pass 后面带斜杠

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

同样的请求:

http://example.com/api/user/info

转发结果:

http://127.0.0.1:8080/user/info

这里 location 匹配到的 /api/ 前缀会被替换掉。

什么时候该带斜杠

看后端服务希望接收什么路径。

后端接口本身就以 /api 开头

例如后端接口地址就是:

http://127.0.0.1:8080/api/user/info

那就不应该带斜杠:

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

后端服务不需要 /api 前缀

例如后端接口实际是:

http://127.0.0.1:8080/user/info

只是希望对外统一暴露为 /api/,那就应该带斜杠:

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

这个规则一定要记牢。很多 404、路径错乱问题,本质上就是这里配置错了。

按路径转发到不同服务

微服务、前后端分离项目里,经常需要按路径把请求分发到不同系统。

server {
    listen 80;
    server_name example.com;

    location /api/ {
        proxy_pass http://127.0.0.1:8080/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }

    location /admin/ {
        proxy_pass http://127.0.0.1:8090/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }

    location / {
        root /usr/share/nginx/html;
        index index.html;
        try_files $uri $uri/ /index.html;
    }
}

这个配置表达的是:

  • /api/ 走后端接口服务
  • /admin/ 走管理后台服务
  • / 走前端静态页面

这类配置在 Vue、React、Angular 前端项目中非常常见。

使用 upstream 管理后端节点

如果后端只有一台机器,直接写 proxy_pass http://127.0.0.1:8080; 就够了。 但只要进入正式环境,通常都建议通过 upstream 管理后端节点。

upstream backend_server {
    server 127.0.0.1:8080;
}

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://backend_server;

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

这样写的好处是:

  • 后端地址统一管理
  • 后续扩容更方便
  • 配置更清晰

反向代理配合负载均衡

Nginx 不只是“转发到一台后端”,还可以把请求分配到多台机器。

upstream backend_cluster {
    server 192.168.10.101:8080;
    server 192.168.10.102:8080;
    server 192.168.10.103:8080;
}

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://backend_cluster;

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

默认情况下,Nginx 会按轮询方式分发请求。

常见负载均衡策略

1. 轮询

默认策略,每个请求依次发给不同服务器。

2. weight 权重

upstream backend_cluster {
    server 192.168.10.101:8080 weight=5;
    server 192.168.10.102:8080 weight=3;
    server 192.168.10.103:8080 weight=2;
}

机器性能不一样时,可以让高性能机器承担更多请求。

3. ip_hash

upstream backend_cluster {
    ip_hash;
    server 192.168.10.101:8080;
    server 192.168.10.102:8080;
}

同一个客户端 IP 会尽量落到同一台机器,适合某些依赖会话粘性的旧系统。

但要明确:现代系统更推荐把会话放到 Redis 或数据库,而不是强依赖 ip_hash

反向代理 WebSocket 的配置

普通 HTTP 代理配置不一定能直接支持 WebSocket。 如果后端有 WebSocket 服务,需要显式配置升级头。

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

    location /ws/ {
        proxy_pass http://127.0.0.1:8081;

        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        proxy_set_header Host $host;
        proxy_read_timeout 600s;
    }
}

关键点有两个:

  • proxy_http_version 1.1
  • UpgradeConnection 请求头

少任何一个,WebSocket 握手都可能失败。

反向代理上传文件时的注意点

默认情况下,Nginx 对请求体大小有限制。上传大文件时,如果不改配置,常见报错是 413 Request Entity Too Large

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

    client_max_body_size 100m;

    location /upload/ {
        proxy_pass http://127.0.0.1:8080/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

client_max_body_size 的作用

它控制客户端请求体的最大允许大小。 比如文件上传、表单提交、大 JSON 请求体,都受它影响。

这个参数应该根据业务实际需要设置,不宜无限放大。

反向代理 HTTPS 的典型配置

线上最常见的是 Nginx 终止 HTTPS,然后转发给后端 HTTP 服务。

server {
    listen 443 ssl;
    server_name example.com;

    ssl_certificate /etc/nginx/ssl/example.com.crt;
    ssl_certificate_key /etc/nginx/ssl/example.com.key;

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

        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 https;
    }
}

server {
    listen 80;
    server_name example.com;
    return 301 https://$host$request_uri;
}

这个配置做了两件事:

  • 443 端口处理 HTTPS 请求,并代理到后端
  • 80 端口全部跳转到 HTTPS

为什么要传 X-Forwarded-Proto

因为后端实际接收到的可能是来自 Nginx 的 HTTP 请求。 如果不告诉后端“用户原始请求其实是 HTTPS”,后端在生成跳转链接时可能错误地返回 HTTP 地址。

常见 location 匹配规则要理解

很多人以为 Nginx 代理问题出在 proxy_pass,实际常常是 location 匹配优先级没搞明白。

先看几种常见写法:

location = /login { ... }
location /api/ { ... }
location ^~ /static/ { ... }
location ~ \.php$ { ... }
location / { ... }

常见含义

  • =:精确匹配
  • 普通前缀匹配:按最长前缀匹配
  • ^~:前缀匹配成功后,不再继续匹配正则
  • ~:区分大小写的正则匹配
  • ~*:不区分大小写的正则匹配

一个容易出错的例子

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

location / {
    root /usr/share/nginx/html;
    index index.html;
}

这里 /api/ 会优先于 /,因为它前缀更长。 但如果又加了正则规则,就可能因为优先级变化导致请求走错位置。 所以复杂配置里,不要只盯着 proxy_pass,一定要一起检查 location 规则。

反向代理时后端获取真实 IP 的问题

只在 Nginx 配 X-Real-IPX-Forwarded-For 还不够,后端框架也要正确解析这些头。

例如 Spring Boot 项目,如果没有正确启用转发头处理,程序里拿到的 IP 可能仍然是代理服务器地址。

这件事本质上分两步:

  1. Nginx 把真实客户端 IP 传过去
  2. 后端框架信任并解析这些头

如果后端拿到的还是代理 IP,不要只查 Nginx,应用框架配置也要一起看。

一份更贴近生产环境的反向代理示例

upstream app_backend {
    server 192.168.10.101:8080 weight=5;
    server 192.168.10.102:8080 weight=5;
}

server {
    listen 80;
    server_name example.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    server_name example.com;

    ssl_certificate /etc/nginx/ssl/example.com.crt;
    ssl_certificate_key /etc/nginx/ssl/example.com.key;

    client_max_body_size 50m;

    location /api/ {
        proxy_pass http://app_backend/;

        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 5s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
    }

    location /ws/ {
        proxy_pass http://app_backend/ws/;

        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-Forwarded-For $proxy_add_x_forwarded_for;

        proxy_read_timeout 600s;
    }

    location / {
        root /usr/share/nginx/html;
        index index.html;
        try_files $uri $uri/ /index.html;
    }
}

这份配置已经覆盖了多数前后端分离项目的核心诉求:

  • HTTP 跳 HTTPS
  • 静态资源直接返回
  • /api/ 反向代理到应用集群
  • 支持 WebSocket
  • 支持大文件上传
  • 传递真实请求头
  • 配置合理超时

部署和生效流程

写完配置后,不要直接重启,先检查语法。

nginx -t

如果输出类似:

syntax is ok
test is successful

说明配置语法没有问题。

然后再重新加载配置:

nginx -s reload

或者使用 systemd 的系统中:

systemctl reload nginx

这里要区分两个动作:

  • reload:重新加载配置,通常不中断现有连接
  • restart:重启服务,可能影响当前请求

线上环境优先使用 reload

常见问题排查

1. 访问出现 404

优先检查这几个点:

  • location 是否匹配到了预期规则
  • proxy_pass 结尾斜杠是否写对
  • 后端接口真实路径是否和转发后的路径一致
  • 后端服务是否真的启动在对应端口

很多所谓“Nginx 配错了”,实际是路径拼接规则没搞清楚。

2. 访问出现 502 Bad Gateway

502 一般表示 Nginx 连不上后端,或者后端返回异常。

常见原因:

  • 后端服务没启动
  • 后端监听地址不对,例如只监听了某个网卡
  • 端口写错
  • 后端进程崩溃
  • 防火墙或安全组拦截

排查方式

先在 Nginx 所在机器上直接请求后端:

curl http://127.0.0.1:8080

如果这里都不通,就不是 Nginx 转发逻辑的问题,而是后端服务本身不可达。

3. 上传文件时报 413

这是请求体太大。 检查 client_max_body_size

4. 后端拿不到真实域名或协议

检查这些头是否正确传递:

proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;

5. WebSocket 连接失败

重点检查:

proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";

6. 前端路由刷新 404

这是单页应用很常见的问题。 例如 Vue Router、React Router 使用 history 模式后,直接刷新 /user/profile,如果 Nginx 只按物理文件查找,就会返回 404。

应使用:

location / {
    root /usr/share/nginx/html;
    index index.html;
    try_files $uri $uri/ /index.html;
}

这样不存在的物理路径会回退到 index.html,再由前端路由接管。

写反向代理配置时的几个实践建议

不要一上来就写复杂规则

先让最简单的代理打通,再逐步加:

  • 请求头
  • 超时
  • HTTPS
  • 负载均衡
  • WebSocket
  • 缓存与限流

复杂配置一次堆满,排错会很困难。

明确“对外路径”和“后端路径”的关系

这决定了 proxy_pass 是否带斜杠,也决定了后端接口该不该保留前缀。

日志一定要看

Nginx 排错不能只靠猜。 要结合:

  • 访问日志 access.log
  • 错误日志 error.log

很多 502、499、504 问题,日志里都会直接给线索。

代理层和应用层要一起看

反向代理问题经常不是单点问题,而是链路问题:

  • Nginx 路径转发是否正确
  • 后端服务是否可达
  • 应用路由是否存在
  • 框架是否正确识别代理头
  • 前端调用地址是否写对

只盯一个地方,往往查不出来。

总结

Nginx 配置反向代理的本质只有一句话:把客户端请求按预期规则稳定地转发给后端服务。 但真正决定配置是否可用的,不是会不会写 proxy_pass,而是你是否清楚下面这些问题:

  • 请求会被哪个 location 匹配
  • 转发后的 URI 到底是什么
  • 是否需要保留原始 Host、客户端 IP、协议头
  • 后端是否需要负载均衡
  • 是否涉及 HTTPS、WebSocket、文件上传、前端路由回退

把这些点理顺后,Nginx 反向代理并不复杂。难点从来不在语法,而在于对请求链路是否真正理解透彻。

正文到此结束
评论插件初始化中...
Loading...