ElIauk

Nginx 反向代理与 upstream

反向代理解决什么 Spring Boot 应用直接对外暴露 8080 端口,能跑,但不够用:要配 HTTPS、要做多实例负载均衡、要隐藏后端地址、要统一处理跨域和超时。这些事情都堆到应用里做,代码会越来越重,而且换个语言就得重写一遍。 Nginx 放在最前面,把 TLS 终止、静态资源、请求转发、限

评论
Nginx 反向代理与 upstream

反向代理解决什么

Spring Boot 应用直接对外暴露 8080 端口,能跑,但不够用:要配 HTTPS、要做多实例负载均衡、要隐藏后端地址、要统一处理跨域和超时。这些事情都堆到应用里做,代码会越来越重,而且换个语言就得重写一遍。

Nginx 放在最前面,把 TLS 终止、静态资源、请求转发、限流、压缩这些"基础设施"的活接过去,后端只处理业务。反向代理最典型的用法就是:浏览器访问 api.example.com,Nginx 把请求转给内网的 8080 和 8081,用户完全感觉不到后端有几台机器。

一个可用的配置

upstream order_backend {
    server 127.0.0.1:8080 weight=1 max_fails=3 fail_timeout=10s;
    server 127.0.0.1:8081 weight=1 max_fails=3 fail_timeout=10s;
    keepalive 32;
}

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

    client_max_body_size 20m;

    location /api/ {
        proxy_pass http://order_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_set_header Connection "";
        proxy_connect_timeout 3s;
        proxy_read_timeout 30s;
        proxy_send_timeout 30s;
    }
}

几个关键点:

  • upstream 定义后端列表,默认是轮询,weight 调整权重,max_fails 和 fail_timeout 实现被动的失败摘除;
  • keepalive 32 让 Nginx 和后端保持长连接,必须配合 proxy_http_version 1.1 和 Connection "" 才生效;
  • proxy_set_header Host $host 保留原始域名,后端生成重定向链接时不会指到内网地址;
  • X-Real-IP 和 X-Forwarded-For 让后端拿到真实客户端 IP,否则日志里全是 Nginx 的地址;
  • X-Forwarded-Proto 告诉后端原始请求是 HTTP 还是 HTTPS,避免重定向循环。

负载均衡策略不止轮询一种:ip_hash 让同一客户端固定落到同一台后端,适合还在用本地会话的应用;least_conn 优先转发给连接数最少的节点,适合长连接或者各接口耗时差异大的场景。被动健康检查靠 max_fails 和 fail_timeout,后端连续失败达到次数就先摘除,过一段时间再尝试恢复。它不如主动探测灵敏,但对大多数业务已经够用。

proxy_pass 的斜杠问题

这是反向代理最容易踩的坑。location 和 proxy_pass 末尾的斜杠组合不同,转发后的路径完全不同:

配置 请求 /api/user/1 转发到
location /api/ { proxy_pass http://backend/; } /user/1
location /api/ { proxy_pass http://backend; } /api/user/1
location /api { proxy_pass http://backend/; } /user/1(前缀被替换)

规则可以记成:proxy_pass 带 URI(包括单独一个 /)时,会用这个 URI 替换掉 location 匹配到的前缀;不带 URI 时,原样转发完整路径。

后端接口如果本来就带 /api 前缀,用第二种;如果后端没有前缀,用第一种。两者混用时,最常见的结果是 404 或者路径里出现 //。

另外 location 的匹配顺序也值得记:= 精确匹配优先级最高,其次是 ^~ 前缀匹配(命中后不再检查正则),然后是正则 ~,最后是普通前缀匹配。写多个 location 时,别假设它们按书写顺序生效。

常见坑

1. 后端拿到的 Host 不对

不设置 proxy_set_header Host $host 时,后端收到的是 upstream 里的地址,生成的下载链接、重定向地址可能指向 127.0.0.1:8080。用户点一下就跳到了本机。

2. 上传大文件报 413

Nginx 默认 client_max_body_size 1m,上传接口超过 1MB 直接返回 413,请求根本到不了后端。在 server 或 location 里调大,并确认后端和网关的限制也放开了。

3. 502 Bad Gateway

含义是 Nginx 连不上后端。常见原因:后端没启动、端口写错、upstream 里的地址解析失败、后端进程崩溃、SELinux 阻止了 Nginx 发起网络连接。先 ss -lntp | grep 8080 确认后端在监听,再用 curl 直连后端排除应用问题。

4. 504 Gateway Timeout

Nginx 连上了后端,但等响应超时。proxy_read_timeout 默认 60 秒,后端接口跑得更久就会 504。不要一上来就把超时调到 600 秒,先确认接口为什么慢;确实是长任务,就改成异步加轮询。

5. WebSocket 连不上

WebSocket 需要额外的头:

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

location /ws/ {
    proxy_pass http://order_backend;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
}

没有 Upgrade 和 Connection,握手会失败,前端看到的是连接被断开。

6. upstream 里的域名只解析一次

server backend.internal:8080; 这种写法在 Nginx 启动时解析一次域名,后端 IP 变了不会自动更新。云环境里要用 resolver 配合变量,或者直接写 IP。

7. 改了配置没生效

nginx -s reload 之前先 nginx -t 检查语法。配置没有 include 到主配置、改的是另一个环境的文件,也是常见原因。

排查步骤

按"配置—连通性—应用"的顺序查:

nginx -t                                  # 语法检查
tail -f /var/log/nginx/error.log          # 错误日志
curl -I http://127.0.0.1:8080/api/health  # 绕过 Nginx 直连后端
curl -H "Host: api.example.com" http://127.0.0.1/api/health
ss -lntp | grep -E '80|8080'

如果直连后端正常、经过 Nginx 异常,问题在代理配置或网络;如果直连也异常,先排查应用。在 access.log 里加上 $upstream_response_time 和 $upstream_status,能直接看出是后端慢还是后端报错。

参考

评论

推荐阅读

订阅邮件

订阅更新,及时收到最新文章。

作者