Nginx upstream模块仅负责负载均衡,内容替换需结合sub_filter、headers_more或Lua模块在proxy_pass后处理;需关闭proxy_buffering并注意gzip兼容性。

Nginx 的 upstream 模块本身不负责响应内容替换,它只做请求分发与后端节点管理。内容替换(如修改 HTML 中的文本、URL、状态码等)属于响应处理阶段,需由 sub_filter、headers_more 或 lua 等模块在 proxy_pass 后的响应流中完成。但你可以把「负载均衡」和「响应内容替换」组合使用——即先通过 upstream 实现高可用分发,再在反向代理环节对返回内容做统一或差异化替换。
下面分三部分讲清楚怎么落地:
upstream 是基础:先定义可靠后端集群
必须放在 http { } 块内,不能嵌套在 server 或 location 里:
- 至少配置两个
server,否则健康检查(max_fails/fail_timeout)不生效 - 推荐显式加
weight和容错参数,避免单点失效影响全局
http {
upstream app_backend {
server 192.168.1.10:8080 weight=3 max_fails=2 fail_timeout=30s;
server 192.168.1.11:8080 weight=2 max_fails=2 fail_timeout=30s;
server 192.168.1.12:8080 backup; # 故障兜底
}
}响应替换要靠 proxy_pass + sub_filter 配合
sub_filter 是 Nginx 官方模块(需编译时启用 --with-http_sub_module),支持正则或字符串替换,作用于响应体(body)。它必须和 proxy_pass 在同一个 location 块中使用,并且要关闭缓冲(否则可能截断或不生效):
-
sub_filter_once off:对整个响应体多次匹配替换 -
sub_filter_types *:不限制 MIME 类型(默认只处理text/html) -
proxy_buffering off:确保流式响应可被实时过滤(关键!)
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://app_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
# 启用响应体替换
sub_filter '<title>My App</title>' '<title>My App (LB)</title>';
sub_filter 'https://old-api.example.com' 'https://api.example.com';
sub_filter_once off;
sub_filter_types *;
proxy_buffering off;
# 可选:添加自定义响应头标识替换已生效
add_header X-Content-Filtered "true";
}
}差异化替换需结合变量或 Lua(进阶)
如果希望「不同后端返回的内容按来源做不同替换」,纯 sub_filter 不行(它对所有上游响应一视同仁)。这时有两个实用路径:
-
用
$upstream_addr区分来源,配合map+sub_filter动态值(较轻量)map $upstream_addr $replace_title { default "<title>My App</title>"; "192.168.1.10:8080" "<title>My App (Node-A)</title>"; "192.168.1.11:8080" "<title>My App (Node-B)</title>"; } # 然后在 location 中用 sub_filter "$replace_title" ... -
用
ngx_http_lua_module做精准控制(推荐生产环境)
可读取$upstream_addr、解析 JSON 响应、动态改写 body,还能加条件逻辑(如仅对 200 响应替换,跳过错误页):location / { proxy_pass http://app_backend; proxy_buffering off; lua_need_request_body on; header_filter_by_lua_block { if ngx.status == 200 then ngx.header["X-Upstream"] = ngx.var.upstream_addr end } body_filter_by_lua_block { local chunk = ngx.arg[1] if chunk and ngx.arg[2] then -- last chunk local replaced = string.gsub(chunk, "old-text", "new-text-on-" .. ngx.var.upstream_addr) ngx.arg[1] = replaced end } }
不复杂但容易忽略:sub_filter 对压缩响应(gzip)无效,务必确认后端没开启 gzip,或 Nginx 自己解压后再过滤(需 gunzip on + gzip_disable "msie6" 等配合)。


















