Nginx反向代理在实际项目中扮演的角色,是把分散在不同端口、不同主机上的后端服务收拢到同一个入口。客户端请求到达Nginx后,Nginx再以代理身份向后端服务发起新请求,把响应取回来交还给客户端。这样可以隐藏后端真实地址、统一日志格式、集中处理TLS证书,也方便做灰度发布和故障切换。

从Nginx内部看,反向代理不是简单的数据转发。它涉及两个独立连接:客户端到Nginx的连接,以及Nginx到上游服务的连接。每个连接的建立时间、读取超时、响应缓冲、请求头重写,都会影响最终体验。只有把这些环节拆开理解,配置时才知道每一行在解决什么问题。
一、先分清正向代理与反向代理
正向代理和反向代理经常被放在一起比较,但二者面向的对象完全不同。正向代理站在客户端一侧,代理的是浏览器或终端程序。当你配置了正向代理后,客户端访问外部网站时,请求先发给代理服务器,由代理服务器去目标站点取数据。正向代理可以帮助内网用户访问外部网络,也可以隐藏客户端的真实出口IP。
反向代理则站在服务端一侧,代理的是后端应用。客户端通常并不知道代理的存在,以为自己直接访问的是业务系统。实际上请求先到Nginx,再由Nginx转发给后端的Tomcat、Node.js、Python服务或其他进程。客户端只能看到Nginx暴露的地址,后端的端口、内部域名、服务器数量都可以对外隐藏。
在Nginx中实现反向代理,核心指向是proxy_pass指令。它隶属于ngx_http_proxy_module模块,这个模块默认已经编译进Nginx。也就是说,只要安装了标准版本的Nginx,不需要额外扩展,就可以直接使用HTTP反向代理能力。更高层的负载均衡、缓存、重试机制,都是围绕这个模块展开的。
二、最小可用的反向代理配置
从零开始配置一个反向代理,只需要一个server块和一个location块。假设本机8080端口已经运行了一个后端服务,想让访问80端口的用户自动获取8080端口的内容,可以使用下面这段配置。
server {
listen 80;
server_name app.ipipp.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;
}
}
这段配置中,listen 80表示Nginx监听80端口,server_name用来匹配请求头中的Host域名。当请求进入location /后,proxy_pass会把请求转发到http://127.0.0.1:8080。后面三行proxy_set_header负责把原始请求头补充完整,让后端服务知道客户端真实IP和原始域名,否则后端拿到的会是Nginx自己的IP。
配置完成后,可以先用nginx -t检查语法,再执行nginx -s reload让配置生效。如果是在容器环境中,也可以使用nginx -s reload或直接重启容器。验证时打开浏览器访问http://app.ipipp.com,如果后端服务正常,就能看到预期内容。
这里有一个非常容易踩坑的细节:proxy_pass末尾是否带斜杠。假设请求URI是/api/user/list,如果写成proxy_pass http://127.0.0.1:8080/;,Nginx会把location匹配到的部分替换为一个斜杠再拼接剩余内容,实际转发上游的路径会变成/user/list。如果写成proxy_pass http://127.0.0.1:8080;,则原始URI会整体保留,上游收到的仍然是/api/user/list。这个差异在前后端分离项目中尤其重要,经常导致404或接口路径不一致。
三、多后端路径分发实战
真实业务不会只有一个后端,常见结构是前端静态页面、API接口、管理后台分别运行在不同端口。Nginx可以通过多个location把不同前缀的路径分发到不同上游。这样外部只需要暴露一个域名,就能访问多个内部服务。
server {
listen 80;
server_name app.ipipp.com;
location / {
proxy_pass http://127.0.0.1:5000;
}
location /api/ {
proxy_pass http://127.0.0.1:8080/;
}
location /admin/ {
proxy_pass http://127.0.0.1:9090/;
}
}
这段配置中,访问根路径/会转发到5000端口的前端服务;访问/api/会转发到8080端口的接口服务;访问/admin/会转发到9090端口的管理后台。注意/api/和/admin/后面的proxy_pass末尾都带了斜杠,这样实际转发时/api/和/admin/前缀会被去掉,后端接收到的路径更干净。如果这里又写成不带斜杠,后端就会收到带前缀的完整路径,需要在应用层再处理一次。
Nginx的location匹配不是从上到下找到哪个算哪个。它有一套优先级规则:精确匹配优先级最高,其次是带正则的location ~或location ~*,再然后是普通前缀匹配,其中最长前缀优先。配置多个location时,不要随意调换顺序,否则可能被正则规则提前拦截。对于路径分发场景,推荐尽量使用普通前缀匹配,只有确实需要模糊匹配时才使用正则。
如果同一个后端服务部署了多个实例,可以使用upstream块定义服务器组,让Nginx在多个实例之间做轮询。比如后端8080和8081都运行着相同服务,可以这样配置。
upstream backend_api {
server 127.0.0.1:8080;
server 127.0.0.1:8081;
}
server {
listen 80;
server_name app.ipipp.com;
location /api/ {
proxy_pass http://backend_api/;
}
}
这里的proxy_pass指向的是upstream定义的组名backend_api。Nginx会按请求顺序轮流把流量分配给8080和8081。默认策略是加权轮询,如果实例性能不同,可以给server加上weight参数,例如server 127.0.0.1:8080 weight=3;表示这个实例承载更多请求。生产环境中通常会结合健康检查参数,避免把请求发给已经挂掉的实例。
四、WebSocket与长连接场景
普通HTTP请求一次请求一次响应,连接复用程度有限。但WebSocket建立后,客户端和服务器之间会保持一条长连接,双向持续传输数据。要让Nginx正确反向代理WebSocket,不能只写proxy_pass,还必须处理升级请求。WebSocket握手时,客户端会发送Upgrade: websocket和Connection: Upgrade两个头,Nginx默认不会自动把这两个头转发给上游。
location /ws/ {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 3600s;
}
这里先把代理协议版本提升到1.1,因为HTTP/1.1才支持协议升级。proxy_set_header Upgrade $http_upgrade会把客户端带来的Upgrade头原样转发。Connection "upgrade"表示告诉上游连接需要升级。最后把proxy_read_timeout调大,避免长连接空闲一段时间后被Nginx主动断开。默认读取超时是60秒,对WebSocket来说太短了,聊天室、在线协作、实时监控等场景都需要大幅调高。
更稳妥的做法是使用map指令动态设置Connection头。因为某些客户端可能不会发送Upgrade头,如果无脑写死Connection: upgrade,普通HTTP请求会出现异常。可以使用下面这段配置。
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 80;
location /ws/ {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_read_timeout 3600s;
}
}
这样当$http_upgrade为空时,Connection会被设置为close,普通请求可以正常结束。当客户端确实携带Upgrade时,连接才会保持升级状态。WebSocket代理调试时,建议优先确认上游服务是否已监听正确端口,再查看Nginx的错误日志中是否有upgrade相关提示,最后再检查浏览器的Network面板是否完成了101 Switching Protocols。
五、缓存、缓冲与故障转移
反向代理不只是转发流量,还可以通过缓冲和缓存减轻上游压力。Nginx默认开启proxy_buffering,当上游响应速度很快、客户端读取速度很慢时,Nginx会先把响应内容写入缓冲区,再逐步发送给客户端。这样上游服务可以快速完成请求处理,不需要被慢客户端长时间占用连接。缓冲区大小可以通过proxy_buffer_size和proxy_buffers调整,但通常情况下默认值已经够用。
对于大文件下载或流式响应,关闭缓冲反而更合适。比如服务器返回的是实时日志、SSE事件流或大文件分块,如果Nginx先把整块响应缓存起来,用户可能迟迟看不到数据。此时可以设置proxy_buffering off;,让Nginx收到多少就转发多少,降低首字节延迟。另外还可以通过proxy_cache_path和proxy_cache配置HTTP缓存,把可复用的响应直接缓存在Nginx侧,减少对上游的重复请求。
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=api_cache:10m max_size=1g inactive=60m use_temp_path=off;
server {
listen 80;
location /static/ {
proxy_pass http://backend_static;
proxy_cache api_cache;
proxy_cache_valid 200 302 10m;
proxy_cache_valid 404 1m;
add_header X-Cache-Status $upstream_cache_status;
}
}
这里的proxy_cache_path定义了缓存目录、缓存层级、内存区域和最大占用空间。proxy_cache_valid针对不同状态码设置缓存时间,200和302响应缓存10分钟,404响应只缓存1分钟。add_header把缓存命中状态暴露给客户端,方便调试时观察是否命中。缓存的清理需要借助proxy_cache_purge或商业版模块,开源版没有直接删除单条缓存的能力,这一点在规划时要提前考虑。
故障转移方面,可以配合upstream中的max_fails和fail_timeout实现简单健康检查。默认情况下,某个上游实例请求失败后,Nginx会继续尝试,直到达到max_fails阈值,才会在fail_timeout时间内暂时把它摘除。示例配置如下。
upstream backend_api {
server 127.0.0.1:8080 max_fails=3 fail_timeout=30s;
server 127.0.0.1:8081 max_fails=3 fail_timeout=30s;
server 127.0.0.1:8082 backup;
}
这段配置表示8080和8081分别允许连续失败3次,超过后30秒内不再分配请求。8082被标记为backup,只有在所有普通实例都不可用时才会启用。临时故障转移还可以配合proxy_next_upstream控制哪些错误会触发重试,例如连接失败、超时或上游返回502时,自动把请求转给下一个实例。重试会带来额外延迟,写多读少的接口可以适当重试,对延迟敏感的接口则应谨慎开启。
反向代理配置最终要回到业务场景本身。路径规则、超时时间、缓冲策略、故障转移机制,都需要和实际后端行为匹配。先把最小配置跑通,再根据日志和监控逐项调整,是避免反向代理配置失控的最稳妥方式。