Nginx反向代理设置:高效引导流量

通过清晰的路由、正确的标头、WebSocket支持、超时、缓冲和故障排除步骤,设置Nginx反向代理。

Nginx反向代理设置:高效引导流量

Nginx反向代理设置让Nginx接收公共网络流量并将其转发到一个或多个后端应用程序。当您的应用程序运行在Node.js、Python、Go、Java或其他不应直接暴露于互联网的服务上时,这非常有用。

用户不再直接连接到您的应用程序端口,而是通过标准的HTTP或HTTPS端口连接到Nginx。Nginx处理公共边缘,然后将流量高效地引导到正确的内部服务。

反向代理的作用

反向代理位于您的应用服务器之前。客户端与Nginx通信,Nginx再与后端通信。对于浏览器而言,Nginx就是网站。对于应用程序而言,Nginx是上游客户端,除非您传递保留原始请求细节的标头。

这种模式为您带来几个好处:

  • 您可以在私有端口(如300050008080)上运行应用程序。
  • 您可以在Nginx处终止TLS。
  • 您可以将不同的主机名或路径路由到不同的服务。
  • 您可以添加缓冲、超时、压缩和缓存。
  • 您可以向公共网络隐藏后端实现细节。

一个运行在127.0.0.1:3000上的应用程序的基本反向代理配置如下:

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://127.0.0.1:3000;
        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_pass指令告诉Nginx将请求发送到哪里。proxy_set_header行保留了有用的请求上下文。没有它们,您的应用程序可能会将每个请求记录为来自Nginx,并且可能不知道原始请求使用的是HTTP还是HTTPS。

如果您不熟悉虚拟主机结构,请在跨多个域拆分流量之前查看Nginx服务器块

按主机或路径路由流量

反向代理规则通常按主机名、路径或两者进行路由。当不同的应用程序使用不同的域时,基于主机的路由很常见:

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

    location / {
        proxy_pass http://127.0.0.1:4000;
        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;
    }
}

当一个域服务于多个服务时,基于路径的路由很有用:

server {
    listen 80;
    server_name example.com;

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

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

注意proxy_pass中的尾部斜杠。在Nginx中,proxy_pass http://backend;proxy_pass http://backend/;在location块内使用时,可能会以不同的方式重写转发的URI。请测试您的应用程序期望的确切URL路径。

例如,如果/api/users意外地以/users/api/api/users的形式到达您的后端,请首先检查location前缀和尾部斜杠的组合。这是最常见的反向代理错误之一。

标头、超时和WebSocket

标头使后端了解原始请求。当应用程序构建绝对URL、验证允许的主机或支持多租户时,Host标头很重要。X-Forwarded-For有助于保留原始客户端IP。X-Forwarded-Proto有助于应用程序在TLS终止后生成安全链接。

如果您的后端使用WebSocket,请添加升级标头:

location /socket/ {
    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;
}

超时应与您的应用程序的行为相匹配。正常的Web请求应快速完成。报告导出、流式端点或长轮询请求可能需要更多时间:

proxy_connect_timeout 5s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;

避免仅仅为了隐藏一个慢速端点而到处设置巨大的超时。长时间的超时可能会占用资源,并使真正的故障更难被发现。请调整需要它的location。

缓冲是另一个重要设置。默认情况下,Nginx可以在将上游响应发送到客户端之前对其进行缓冲。这对许多Web应用程序很有帮助,但流式端点可能需要禁用缓冲:

proxy_buffering off;

仅在需要流式行为的地方使用此设置。对于标准的HTML和API响应,缓冲通常可以提高稳定性。

TLS终止和HTTPS重定向

在许多设置中,Nginx也处理HTTPS。这让后端应用程序在私有HTTP端口上运行,而用户在端口443上获得正常的安全站点。

常见的配置如下:

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

server {
    listen 443 ssl;
    server_name example.com;

    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:3000;
        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;
    }
}

重定向服务器块故意很小。它只做一件事:将纯HTTP流量移动到HTTPS。HTTPS服务器块处理代理。

如果您的应用程序位于Nginx后面,但仍然生成http://链接,请检查它是否信任X-Forwarded-Proto。许多框架需要诸如“trust proxy”或允许的代理列表之类的设置,然后才能使用转发的标头。不要在应用程序层盲目信任来自公共互联网的转发标头;确保只有Nginx可以访问应用程序端口。

上游组和简单负载均衡

当一个后端不够用时,定义一个上游组:

upstream app_backend {
    server 10.0.1.10:3000;
    server 10.0.1.11:3000;
    keepalive 32;
}

server {
    listen 80;
    server_name example.com;

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

开源Nginx默认使用轮询负载均衡。当长时间请求使一个后端比另一个更繁忙时,您也可以使用least_conn等选项。开源Nginx中的健康检查主要是被动的:如果后端失败,Nginx可以根据失败设置将其标记为在一段时间内不可用。Nginx Plus具有主动健康检查,但不要假设这些功能存在于每个安装中。

上游块中的Keepalive保持后端连接打开以供重用。这有助于处理大量小请求,但后端必须能够处理Nginx可能保持的空闲和活动连接数。

容器和私有网络

在Docker或Kubernetes中,反向代理设置经常令人困惑,因为localhost的含义发生了变化。如果Nginx在一个容器内运行,127.0.0.1:3000指向Nginx容器本身,而不是一个单独的应用程序容器。

在Docker Compose中,代理到服务名称:

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

在Kubernetes中,您通常代理到服务DNS名称,尽管许多Kubernetes部署使用Ingress控制器而不是手写的Nginx服务器块。

简单的规则是:从Nginx运行的位置测试连接,而不是从您的笔记本电脑或后端容器。如果这失败,Nginx也会失败:

curl -v http://app:3000/

根据您的部署,在Nginx容器内或Nginx主机上运行此命令。

值得检查的安全边界

反向代理应减少公共暴露,而不是意外地增加暴露。后端应用程序通常应在私有接口、私有子网或容器网络上监听。如果您的应用程序在公共VM上监听0.0.0.0:3000,用户可能通过访问http://example.com:3000完全绕过Nginx。

检查主机上的监听端口:

sudo ss -ltnp

如果后端必须在容器内监听所有接口,请使用防火墙规则、安全组或容器网络设置,以便只有Nginx可以从外部访问它。这很重要,因为应用程序通常依赖Nginx进行TLS、请求大小限制、速率限制、身份验证网关或IP允许列表。

还要小心转发的标头。诸如X-Forwarded-For之类的标头很容易被客户端伪造,除非Nginx覆盖它们并且应用程序只信任代理。常见的Nginx模式是:

proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

这会将客户端地址附加到链中。您的应用程序或日志记录管道应知道哪些代理地址是受信任的。否则,速率限制或审计日志可能会记录错误的“客户端”IP。

请求大小限制也属于这个讨论。如果您的应用程序接受文件上传,请有意设置client_max_body_size

client_max_body_size 25m;

除非每个路由都需要,否则不要将其全局提高到巨大的值。个人资料照片上传端点和JSON登录端点不应需要相同的请求体限制。

实用部署清单

在宣布反向代理完成之前,像用户和操作员一样测试它:

  • curl -I http://example.com/ 应显示预期的重定向或响应。
  • curl -I https://example.com/ 应显示预期的状态和标头。
  • 应用程序日志应显示原始主机和有用的客户端IP。
  • WebSocket或流式端点应单独测试。
  • 错误的路径,如/api/does-not-exist,应以您的应用程序期望的方式失败。
  • 在正常请求期间,Nginx错误日志应保持安静。

对于路径路由,我喜欢为每个location测试三个URL:裸前缀、一个普通嵌套路径和一个带查询字符串的路径。例如:

curl -i http://example.com/api/
curl -i http://example.com/api/users
curl -i 'http://example.com/api/users?page=2'

这些简单的检查可以在用户之前捕获许多尾部斜杠错误。

当您重新加载时,每次使用相同的安全序列:

sudo nginx -t
sudo systemctl reload nginx
sudo tail -n 50 /var/log/nginx/error.log

如果应用程序位于TLS终止后面,还要验证生成的链接、重定向、cookie和回调URL是否使用HTTPS。登录流程通常是首先出现问题的地方,因为重定向和安全cookie依赖于应用程序理解原始方案。

常见故障模式

502 Bad Gateway通常意味着Nginx到达了反向代理位置,但无法从上游获得有效响应。后端可能已关闭,端口可能错误,应用程序可能在不同的接口上监听,或者连接可能被防火墙拒绝。

504 Gateway Timeout通常意味着Nginx连接到了某个东西,但没有及时收到响应。这可能是应用程序慢、数据库查询阻塞、工作池过载或端点的超时太短。对于已知的长时间运行的导出端点,增加proxy_read_timeout可能是合适的。它不能修复通常很慢的应用程序。

重定向循环通常来自TLS终止和应用程序信任设置之间的不匹配。浏览器通过HTTPS到达Nginx,Nginx通过HTTP代理到应用程序,应用程序认为原始请求是纯HTTP。应用程序重定向到HTTPS,但同样的事情再次发生。传递X-Forwarded-Proto只是修复的一半;应用程序还必须信任来自代理的它。

缺少客户端IP通常表现为每个请求都来自127.0.0.1、Docker桥接地址或私有负载均衡器地址。传递X-Real-IPX-Forwarded-For,然后配置应用程序和日志记录层以安全地读取它们。

路径路由后损坏的静态资源通常来自假设它们位于/的应用程序。如果您将应用程序挂载在/admin/下,它可能仍然生成指向/assets/app.css的链接。您有时可以通过应用程序基本路径设置来修复此问题。尝试在Nginx中重写每个资源路径通常是脆弱的。

一个小型真实世界示例

想象一台VM运行三个服务:

  • 一个营销站点在127.0.0.1:3000
  • 一个API在127.0.0.1:4000
  • 一个管理工具在127.0.0.1:5000

您可以像这样路由它们:

server {
    listen 443 ssl;
    server_name example.com;

    location /api/ {
        proxy_pass http://127.0.0.1:4000/;
        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;
    }

    location /admin/ {
        proxy_pass http://127.0.0.1:5000/;
        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;
    }

    location / {
        proxy_pass http://127.0.0.1:3000;
        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;
    }
}

这可以工作,但有取舍。API和管理应用程序都必须在其前缀下正确运行。如果它们不这样做,单独的主机名如api.example.comadmin.example.com可能更清晰。好的反向代理设计不仅仅是让Nginx接受配置;而是选择您的应用程序可以接受的路由。

测试和故障排除设置

在重新加载之前始终测试配置:

nginx -t

然后重新加载Nginx并通过公共主机名发出请求。检查浏览器和日志。Nginx访问日志显示请求是否到达Nginx。错误日志显示连接失败、上游超时和错误网关详细信息。

一个实际示例:您的Node.js应用程序在curl http://127.0.0.1:3000上运行良好,但公共站点显示502 Bad Gateway。这意味着Nginx是可访问的,但无法成功与上游通信。检查应用程序是否在预期的地址上监听,端口是否正确,以及本地防火墙是否阻止了连接。

常见的反向代理问题包括:

  • 上游端口或地址错误。
  • 当Nginx在另一个容器中运行时,后端绑定到localhost
  • 缺少WebSocket升级标头。
  • 应用程序因Host标头意外而拒绝请求。
  • 由尾部斜杠引起的URI重写错误。
  • 对于慢速端点,超时太短。

对于更深入的上游故障,请使用Nginx 502故障排除

何时寻求帮助

如果反向代理跨越多个容器、私有网络、TLS证书或负载均衡的上游,请向DevOps工程师寻求帮助。这些设置可能以看起来像Nginx问题的方式失败,但实际上是DNS、防火墙、容器网络或应用程序健康问题。

在通过公共反向代理暴露管理面板、内部API或暂存服务之前,您还应该寻求帮助。小的路由错误可能会造成严重的访问问题。

Nginx反向代理设置是Web基础设施中最有用的模式之一。保持路由清晰,传递正确的标头,仔细测试路径行为,让Nginx成为后端服务的稳定公共入口点。