Nginx 如何实现正向代理、反向代理与透明代理?一文讲透负载均衡、主备冗余与 Docker 化部署
摘要:Nginx 到底是正向代理还是反向代理?透明代理怎么做到"客户端和服务器完全无感"?多台后端主机如何做负载均衡与主备冗余?老旧的裸机部署又该如何平滑迁移到 Docker?
本文从三种代理的本质区别讲起,逐一给出可直接复制运行的配置,覆盖正向代理(含 HTTPS CONNECT)、反向代理、透明代理(应用层 / 网络层两种方案)、多主机负载均衡、主备冗余(backup + keepalived),最后完整演示迁移到 Docker 部署的全流程与踩坑清单。
目录
- 一、先搞清楚:三种代理到底"代理"了谁?
- 二、环境与前置准备
- 三、Nginx 正向代理怎么实现?
- 四、Nginx 反向代理怎么实现?
- 五、透明代理怎么实现(客户端与服务器完全无感)?
- 六、多主机负载均衡怎么配?
- 七、主备冗余怎么做?
- 八、如何迁移到 Docker 部署?
- 九、常见问题 FAQ
- 十、总结与最佳实践
一、先搞清楚:三种代理到底"代理"了谁?
很多同学刚上手时最大的困惑不是"怎么写配置",而是"这三种代理到底有什么区别,我到底该用哪个"。先用一句话把本质钉死:
| 代理类型 | 代理的是谁 | 客户端是否感知代理存在 | 服务器是否感知代理存在 | 典型场景 |
|---|---|---|---|---|
| 正向代理 | 代理客户端去访问外网 | ✅ 知道(要手动配代理地址) | ❌ 不知道(以为在直连客户端) | 内网科学上网、统一出口、访问控制 |
| 反向代理 | 代理服务器对外提供服务 | ❌ 不知道(以为在访问真实站点) | ✅ 知道(自己部署的) | 网关、负载均衡、SSL 卸载 |
| 透明代理 | 在网络层劫持流量转发 | ❌ 完全不知道(零配置) | ❌ / 部分(取决是否保留源 IP) | 企业出口审计、旁路引流、服务网格 |
1.1 正向代理(Forward Proxy)
客户端主动配置代理服务器地址,所有请求先发给代理,再由代理替你访问目标站点。
[客户端] --配置代理--> [正向代理] --> [目标网站]特点:客户端知道代理存在,服务端只看到代理的 IP。
1.2 反向代理(Reverse Proxy)
客户端访问的是代理服务器的地址,代理服务器再把请求转发给后端的真实业务服务器。
[客户端] --> [反向代理] --> [后端服务器集群]特点:客户端以为代理就是真实服务器,代理对客户端隐藏了后端拓扑。
1.3 透明代理(Transparent Proxy)
介于两者之间:客户端不需要任何配置(网关或策略路由把流量导过来),代理在背后悄悄转发。
[客户端] --(网关指向代理)--> [透明代理] --> [目标网站]特点:客户端完全无感;而"服务器是否无感"取决于你用的是应用层透明还是网络层透明(保留真实源 IP)——这一点是本文的重点,后面会单独展开。
一句话记忆:正向代理"替客户端办事",反向代理"替服务器挡枪",透明代理"谁都没告诉就把事办了"。
二、环境与前置准备
本文示例基于如下环境,其他版本大同小异:
| 组件 | 版本 |
|---|---|
| 操作系统 | Ubuntu 22.04 / CentOS 7+ |
| Nginx | 1.24+(建议 1.24.0 或 1.26.x) |
| Docker | 24.0+ |
| Docker Compose | v2.x |
目录约定(后文 Docker 迁移会复用这个结构):
/etc/nginx/ ├── nginx.conf# 主配置├── conf.d/# 各站点配置│ ├── forward.conf# 正向代理│ ├── reverse.conf# 反向代理│ └── transparent.conf# 透明代理└── upstreams/# 负载均衡 / 主备后端└── backend.conf基础工具安装:
# Ubuntu / Debiansudoaptupdate&&sudoaptinstall-ynginxcurlnet-tools# CentOS / RHELsudoyuminstall-yepel-release&&sudoyuminstall-ynginxcurlnet-tools三、Nginx 正向代理怎么实现?
问题:Nginx 主要被设计成反向代理,那它能做正向代理吗?
答案:能,但需要分类讨论——HTTP 可以"伪正向代理",HTTPS 需要第三方模块。
3.1 HTTP 正向代理(利用$http_host动态转发)
原理:正向代理请求里带有完整的Host头,Nginx 用变量$http_host动态解析目标并转发。
# /etc/nginx/conf.d/forward.conf server { listen 8088; resolver 8.8.8.8 ipv6=off; # DNS 解析器,必须配置,否则无法解析变量中的域名 resolver_timeout 5s; access_log /var/log/nginx/forward_proxy.log; location / { # 关键:用变量拼接目标地址,强制触发运行时 DNS 解析 proxy_pass http://$http_host$request_uri; proxy_set_header Host $http_host; # 让代理服务器本身不缓存、不改内容 proxy_buffering off; proxy_request_buffering off; proxy_set_header Proxy-Connection ""; } }⚠️注意:这种写法只支持 HTTP(80 端口)。因为 HTTPS 的请求内容是加密的,Nginx 看不到
Host,无法知道要转发到哪里。
3.2 HTTPS 正向代理(HTTP CONNECT 隧道)
要支持 HTTPS,必须让 Nginx 处理 HTTP 的CONNECT方法建立隧道。原生 Nginx不支持CONNECT,需要引入第三方模块ngx_http_proxy_connect_module。
步骤一:编译带模块的 Nginx
# 依赖sudoaptinstall-ybuild-essential libpcre3-dev zlib1g-dev libssl-devgit# 下载 nginx 源码与模块cd/usr/local/srcwgethttps://nginx.org/download/nginx-1.24.0.tar.gz&&tar-zxvfnginx-1.24.0.tar.gzgitclone https://github.com/chobits/ngx_http_proxy_connect_module.git# 打补丁(patch 文件名要按你的 nginx 版本选择,目录里有对应关系)cdnginx-1.24.0 patch-p1<../ngx_http_proxy_connect_module/patch/proxy_connect_rewrite_102101.patch# 编译安装./configure\--prefix=/etc/nginx\--sbin-path=/usr/sbin/nginx\--conf-path=/etc/nginx/nginx.conf\--with-http_ssl_module\--with-http_v2_module\--with-http_realip_module\--with-stream\--with-stream_ssl_preread_module\--add-module=../ngx_http_proxy_connect_modulemake&&sudomakeinstall步骤二:配置支持 CONNECT 的 server
# /etc/nginx/conf.d/forward.conf server { listen 8088; resolver 8.8.8.8 ipv6=off; resolver_timeout 5s; access_log /var/log/nginx/forward_proxy.log; # 启用 CONNECT 方法(主要用于 HTTPS 隧道) proxy_connect; proxy_connect_allow 443 563; # 允许 CONNECT 的目标端口,all 表示全部 proxy_connect_connect_timeout 10s; proxy_connect_read_timeout 10s; proxy_connect_send_timeout 10s; # 非 CONNECT 请求(即普通 HTTP)走这里 location / { proxy_pass http://$http_host$request_uri; proxy_set_header Host $http_host; } }3.3 验证正向代理
# 测试 HTTPcurl-xhttp://127.0.0.1:8088 http://httpbin.org/ip# 测试 HTTPS(走 CONNECT 隧道)curl-v-xhttp://127.0.0.1:8088 https://httpbin.org/ip浏览器侧的设置:网络设置 → 手动代理 → HTTP 代理填代理机IP:8088即可。
3.4 正向代理的常见坑
- 不配
resolver:proxy_pass里带变量时不写resolver,Nginx 无法解析域名,直接 502。 $http_hostvs$host:$host不带端口,$http_host带端口,转发时用$http_host更准。- HTTPS 只走 CONNECT:别指望用
location里的proxy_pass处理 HTTPS,握手都过不去。
四、Nginx 反向代理怎么实现?
问题:客户端只想访问一个域名,后端却有好几台机器,怎么优雅地转发并让后端拿到真实客户端 IP?
4.1 最小可用配置
# /etc/nginx/conf.d/reverse.conf server { listen 80; server_name www.example.com; location / { proxy_pass http://127.0.0.1:8080; # 后端真实服务地址 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; proxy_read_timeout 60s; proxy_connect_timeout 5s; } }4.2 关键指令逐条拆解
| 指令 | 作用 | 是否必须 |
|---|---|---|
proxy_pass | 指定后端地址,决定转发目标 | ✅ 必须 |
proxy_set_header Host | 让后端拿到原始域名(多站点后端必须) | ✅ 强烈建议 |
X-Real-IP | 传递真实客户端 IP | ✅ 建议 |
X-Forwarded-For | 追加代理链上的 IP,便于溯源 | ✅ 建议 |
X-Forwarded-Proto | 告诉后端原始协议是 http 还是 https | ✅ 建议 |
proxy_http_version 1.1 | 支持长连接、WebSocket | 建议 |
proxy_buffering | 是否缓冲后端响应 | 按需 |
4.3 让后端识别真实客户端 IP
后端(无论 Nginx、Tomcat 还是 PHP)如果要做限流、审计,就需要解出真实 IP:
# 后端 Nginx 上配置 set_real_ip_from 10.0.0.0/8; # 信任的代理机网段 real_ip_header X-Forwarded-For; real_ip_recursive on;五、透明代理怎么实现(客户端与服务器完全无感)?
这是全文最容易翻车的部分。先记住一个核心结论:
Nginx 的"透明代理"分两个等级——应用层透明(客户端无感,配起来简单)和网络层透明(客户端+服务器都无感,保留真实源 IP,但链路极其脆弱)。
| 等级 | 实现手段 | 客户端无感 | 服务器无感(保留源IP) | 复杂度 |
|---|---|---|---|---|
| 应用层透明 | iptables REDIRECT+ Nginx 反代(按 Host/SNI 分流) | ✅ | ❌(源 IP 变为代理机) | ⭐⭐ |
| 网络层透明 | iptables TPROXY+ Nginxproxy_bind ... transparent | ✅ | ✅ | ⭐⭐⭐⭐⭐ |
5.1 前置:让流量"自动"流向代理
客户端完全无感的前提是——流量在客户端毫不知情的情况下被送到代理机。两种常见做法:
(1) 客户端把默认网关指向代理机(适合可控的客户端网段):
# 在客户端执行(示意)iprouteadddefault via192.168.1.10(2) 在代理机所在网段的上一跳路由器上做策略路由,把目标流量引到代理机。
流量到了代理机后,再由iptables拦截并交给 Nginx。
5.2 方案一:应用层透明(简单、推荐 95% 场景)
原理:用iptables的REDIRECT把本机接收到的 80/443 流量重定向到 Nginx 监听端口;Nginx 通过 HTTP 的Host头(80)或 TLS 的SNI(443)判断真实目标,再反向代理过去。
# 开启转发sudosysctl-wnet.ipv4.ip_forward=1# HTTP:把入境 80 重定向到 Nginx 的 8080sudoiptables-tnat-APREROUTING-ptcp--dport80-jREDIRECT --to-ports8080# HTTPS:把入境 443 重定向到 Nginx 的 8443sudoiptables-tnat-APREROUTING-ptcp--dport443-jREDIRECT --to-ports8443Nginx 侧——HTTP 按 Host 分流:
# 透明代理 - HTTP server { listen 8080; resolver 8.8.8.8 ipv6=off; location / { proxy_pass http://$http_host$request_uri; proxy_set_header Host $http_host; } }HTTPS 按 SNI 分流(利用stream+ssl_preread,握手前就能读到域名,这是"无感"的关键):
# 透明代理 - HTTPS(四层,按 SNI 分流) stream { # 根据 SNI 把流量分给不同后端 map $ssl_preread_server_name $transparent_backend { ~example\.com example_upstream; ~api\.example\.com api_upstream; default default_upstream; } upstream example_upstream { server 10.0.0.11:443; } upstream api_upstream { server 10.0.0.12:443; } upstream default_upstream { server 10.0.0.99:443; } server { listen 8443; ssl_preread on; # 开启 SNI 预读 proxy_pass $transparent_backend; proxy_timeout 30s; } }⚠️方案一的代价:客户端确实无感了,但服务器看到的是代理机的 IP(因为 REDIRECT 后 Nginx 以自己身份建连)。要让服务器也"无感",必须上方案二。
5.3 方案二:网络层透明(TPROXY + 保留真实源 IP)
原理:TPROXY在内核层拦截流量并保留原始源 IP + 原始目的 IP,Nginx 用特制 socket 选项IP_TRANSPARENT接收,并用proxy_bind $remote_addr transparent以"客户端身份"去连接后端——这样服务器看到的源 IP 就是真实客户端 IP,端到端完全透明。
第一步:内核与转发参数
sudosysctl-wnet.ipv4.ip_forward=1sudosysctl-wnet.ipv4.conf.all.rp_filter=0# 关闭反向路径过滤,否则丢包sudosysctl-wnet.ipv4.conf.eth0.rp_filter=0# 对监听网卡也关掉# 持久化(可选)cat<<'EOF'|sudotee/etc/sysctl.d/99-transparent.confnet.ipv4.ip_forward = 1 net.ipv4.conf.all.rp_filter = 0 net.ipv4.conf.eth0.rp_filter = 0 EOF第二步:iptables + 策略路由
# 1) 建立 DIVERT 链,处理"已建立连接"的后续包,避免被重复拦截sudoiptables-tmangle-NDIVERTsudoiptables-tmangle-APREROUTING-ptcp-msocket-jDIVERTsudoiptables-tmangle-ADIVERT-jMARK --set-xmark 0x1/0xffffffffsudoiptables-tmangle-ADIVERT-jACCEPT# 2) 把入境 80/443 的流量 TPROXY 到 Nginx 的 12345/12346 端口sudoiptables-tmangle-APREROUTING-ptcp--dport80-jTPROXY --on-port12345--tproxy-mark 0x1/0x1sudoiptables-tmangle-APREROUTING-ptcp--dport443-jTPROXY --on-port12346--tproxy-mark 0x1/0x1# 3) 策略路由:给带标记的包(即代理转发出去的包)指定专用路由表sudoipruleaddfwmark 0x1 lookup100sudoiprouteaddlocal0.0.0.0/0 dev lo table100第三步:Nginx 配置(proxy_bind ... transparent)
# 网络层透明代理 —— HTTP stream { upstream http_backend { server 10.0.0.20:80; } server { listen 12345; proxy_pass http_backend; # 关键:以客户端真实 IP 作为源地址去连接后端 proxy_bind $remote_addr transparent; proxy_timeout 60s; } }# 网络层透明代理 —— HTTPS(按 SNI 分流 + 保留源 IP) stream { map $ssl_preread_server_name $sni_backend { ~example\.com example_https; default default_https; } upstream example_https { server 10.0.0.11:443; } upstream default_https { server 10.0.0.99:443; } server { listen 12346; ssl_preread on; proxy_pass $sni_backend; proxy_bind $remote_addr transparent; # 保留真实源 IP proxy_timeout 60s; } }第四步:给 Nginx 提权
proxy_bind ... transparent需要设置IP_TRANSPARENT,必须以 root(或具备CAP_NET_ADMIN)运行 worker:
# /etc/nginx/nginx.conf 顶层 user root; # 生产环境更推荐用 cap 方式,见下文 Docker 部分5.4 透明代理排错清单
| 现象 | 排查方向 |
|---|---|
| 连接被拒 / 无响应 | xt_TPROXY模块是否加载:lsmod | grep TPROXY |
日志里只有connection refused | 检查proxy_bind权限、nginx 是否以 root 运行 |
| 抓包有包但 Nginx 没收到 | ip rule/ip route策略路由是否配好 |
| 回程丢包 | rp_filter=0是否生效(all和具体网卡都要设) |
| HTTPS 分流不生效 | ssl_preread on是否开启,SNI 是否被客户端禁用 |
| 服务器仍看到代理 IP | 是否遗漏proxy_bind $remote_addr transparent |
务实建议:如果你只是要"客户端无感",方案一(REDIRECT + Host/SNI)就够了,稳定且好维护;只有确实需要把真实客户端 IP 透传到后端(如风控、审计)时,才上 TPROXY 方案二,并且一定要做好压测和回滚预案。
六、多主机负载均衡怎么配?
问题:后端有多台机器,怎么把请求分摊出去,还支持权重、会话保持和健康检查?
Nginx 的负载均衡靠upstream块实现,常用策略如下。
6.1 轮询 / 加权轮询(默认)
upstream backend { server 10.0.0.21:8080; # 默认权重 1 server 10.0.0.22:8080 weight=3; # 权重 3,摊到约 3 倍流量 server 10.0.0.23:8080 weight=2; }6.2 最少连接(适合请求耗时差异大的场景)
upstream backend { least_conn; server 10.0.0.21:8080; server 10.0.0.22:8080; }6.3 IP 哈希 / 一致性哈希(会话保持)
upstream backend { ip_hash; # 同一客户端 IP 固定打到同一台(简单会话保持) server 10.0.0.21:8080; server 10.0.0.22:8080; } # 或使用一致性哈希(增删节点时影响面更小) upstream backend { hash $request_uri consistent; server 10.0.0.21:8080; server 10.0.0.22:8080; }6.4 被动健康检查参数
开源版 Nginx 提供的是被动健康检查(依赖max_fails/fail_timeout):
upstream backend { server 10.0.0.21:8080 max_fails=3 fail_timeout=30s; server 10.0.0.22:8080 max_fails=3 fail_timeout=30s; }含义:30 秒内失败 3 次,就把该节点摘除 30 秒。
如果需要主动健康检查(定时探测
/health),开源版需借助nginx_upstream_check_module模块,或直接使用 Nginx Plus / 其他 LB。
6.5 完整 upstream + 反代示例
upstream backend { least_conn; server 10.0.0.21:8080 weight=3 max_fails=3 fail_timeout=30s; server 10.0.0.22:8080 weight=2 max_fails=3 fail_timeout=30s; server 10.0.0.23:8080 weight=1 max_fails=3 fail_timeout=30s; keepalive 32; # 与后端保持长连接,降低握手开销 } server { listen 80; server_name www.example.com; location / { proxy_pass http://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; } }七、主备冗余怎么做?
问题:负载均衡解决了"分摊",但如果主节点挂了怎么办?Nginx 自己挂了又怎么办?
要分两个层次来看。
7.1 层次一:后端主备(backup)
用backup标记备用节点——只有所有主节点都不可用时,备用节点才会被启用:
upstream backend { server 10.0.0.21:8080; # 主 server 10.0.0.22:8080; # 主 server 10.0.0.30:8080 backup; # 备(平时不接流量) }配合持久化连接时也可以做多级:
upstream backend { server 10.0.0.21:8080 max_fails=2 fail_timeout=10s; server 10.0.0.22:8080 max_fails=2 fail_timeout=10s; server 10.0.0.30:8080 backup; # 兜底 }7.2 层次二:Nginx 自身高可用(keepalived + VIP)
两台 Nginx 组成主备,对外暴露一个虚拟 IP(VIP),谁活着谁持有 VIP:
[ VIP 192.168.1.100 ] / \ [Nginx-1 MASTER] [Nginx-2 BACKUP]在两台 Nginx 上安装 keepalived:
sudoaptinstall-ykeepalived/etc/keepalived/keepalived.conf(MASTER 节点):
global_defs { router_id nginx_ha } # Nginx 存活探针 vrrp_script chk_nginx { script "/etc/keepalived/check_nginx.sh" interval 2 weight -20 # 探测失败则优先级减 20,触发切换 fall 2 rise 2 } vrrp_instance VI_1 { state MASTER interface eth0 virtual_router_id 51 priority 100 # BACKUP 节点设为 90 advert_int 1 authentication { auth_type PASS auth_pass 1111 } track_script { chk_nginx } virtual_ipaddress { 192.168.1.100/24 } }/etc/keepalived/check_nginx.sh:
#!/bin/bash# 返回非 0 表示 nginx 异常,keepalived 会降低本机优先级触发切换if!killall-0nginx2>/dev/null;thenexit1ficurl-sf-o/dev/null http://127.0.0.1/health||exit1exit0sudochmod+x /etc/keepalived/check_nginx.shsudosystemctlenable--nowkeepalived这样,即使一台 Nginx 整体宕机,VIP 也会在秒级漂移到备份机,对外服务几乎无中断。
八、如何迁移到 Docker 部署?
问题:裸机上的 Nginx(尤其是自己编译带第三方模块的那套)怎么平滑迁移到 Docker?
8.1 迁移思路
- 配置与镜像解耦:配置文件通过 volume 挂载,不进镜像;
- 需要自定义模块的,自制镜像(如正向代理的
proxy_connect); - 标准功能的,直接用官方镜像;
- 透明代理容器要特殊授权(
NET_ADMIN/ host 网络); - 用
docker-compose编排,healthcheck+restart保活。
8.2 目录规划
/opt/nginx-docker/ ├── docker-compose.yml ├── Dockerfile# 需要自定义模块时才用├── conf/ │ ├── nginx.conf │ └── conf.d/ │ ├── reverse.conf │ └── transparent.conf └── logs/8.3 方案 A:标准 Nginx(官方镜像,最简单)
docker-compose.yml:
services:nginx:image:nginx:1.24-alpinecontainer_name:nginxports:-"80:80"-"443:443"volumes:-./conf/nginx.conf:/etc/nginx/nginx.conf:ro-./conf/conf.d:/etc/nginx/conf.d:ro-./logs:/var/log/nginxrestart:unless-stoppedhealthcheck:test:["CMD","nginx","-t"]interval:30stimeout:5sretries:3启动:
dockercompose up-ddockercompose logs-fnginx8.4 方案 B:带第三方模块(自制镜像)
以正向代理需要的ngx_http_proxy_connect_module为例:
# Dockerfile FROM debian:bookworm-slim AS build ARG NGINX_VERSION=1.24.0 RUN apt-get update && apt-get install -y --no-install-recommends \ build-essential libpcre3-dev zlib1g-dev libssl-dev \ wget ca-certificates git \ && rm -rf /var/lib/apt/lists/* WORKDIR /build RUN wget -q https://nginx.org/download/nginx-${NGINX_VERSION}.tar.gz \ && tar -zxf nginx-${NGINX_VERSION}.tar.gz \ && git clone --depth=1 https://github.com/chobits/ngx_http_proxy_connect_module.git WORKDIR /build/nginx-${NGINX_VERSION} # 注意:patch 文件名需按 nginx 版本选择 RUN patch -p1 < ../ngx_http_proxy_connect_module/patch/proxy_connect_rewrite_102101.patch \ && ./configure \ --prefix=/etc/nginx \ --sbin-path=/usr/sbin/nginx \ --conf-path=/etc/nginx/nginx.conf \ --with-http_ssl_module \ --with-http_v2_module \ --with-http_realip_module \ --with-stream \ --with-stream_ssl_preread_module \ --with-stream_realip_module \ --add-module=../ngx_http_proxy_connect_module \ && make -j"$(nproc)" && make install FROM debian:bookworm-slim COPY --from=build /etc/nginx /etc/nginx COPY --from=build /usr/sbin/nginx /usr/sbin/nginx COPY --from=build /usr/lib/nginx /usr/lib/nginx RUN mkdir -p /var/log/nginx && ln -sf /dev/stdout /var/log/nginx/access.log \ && ln -sf /dev/stderr /var/log/nginx/error.log EXPOSE 80 443 8088 STOPSIGNAL SIGQUIT CMD ["nginx", "-g", "daemon off;"]docker-compose.yml:
services:nginx-proxy:build:.container_name:nginx-proxyports:-"80:80"-"443:443"-"8088:8088"# 正向代理端口volumes:-./conf/nginx.conf:/etc/nginx/nginx.conf:ro-./conf/conf.d:/etc/nginx/conf.d:rorestart:unless-stopped8.5 方案 C:透明代理容器(重点!)
透明代理需要host 网络 + 内核能力,普通桥接网络会失效:
services:nginx-transparent:image:nginx:1.24-alpinecontainer_name:nginx-transparentnetwork_mode:host# 必须:透明代理要看真实网卡/源 IPcap_add:-NET_ADMIN# 配置 iptables / IP_TRANSPARENT-NET_RAWvolumes:-./conf/nginx.conf:/etc/nginx/nginx.conf:ro-./conf/conf.d:/etc/nginx/conf.d:rorestart:unless-stopped关于 iptables 规则:
network_mode: host的容器与宿主机共享网络命名空间,容器里执行iptables会影响宿主机。更稳妥的做法是把 TPROXY/REDIRECT 规则放在宿主机,或使用nsenter进入宿主网络命名空间统一管理。关于 sysctl:
net.ipv4.ip_forward、rp_filter等参数属于宿主机内核,建议在宿主机/etc/sysctl.d/配置,而不是依赖 compose 的sysctls(可能被忽略)。
8.6 配置热更新与运维
# 校验配置(不重启容器)dockercomposeexecnginx nginx-t# 热加载(不断连接)dockercomposeexecnginx nginx-sreload# 更新镜像并滚动重启dockercompose pull&&dockercompose up-d日志:Docker 里建议把access.log/error.log软链到/dev/stdout、/dev/stderr(见上面 Dockerfile),再用docker logs统一查看。
九、常见问题 FAQ
Q1:Nginx 到底能不能做正向代理?
能。HTTP 用$http_host变量转发即可;HTTPS 必须引入ngx_http_proxy_connect_module支持CONNECT方法。
Q2:透明代理为什么"服务器无感"这么难?
因为默认转发时 Nginx 用自己的 IP 去连后端,服务器就看到了代理 IP。要做到服务器也无感,必须用TPROXY + proxy_bind $remote_addr transparent保留真实源 IP,链路长、依赖内核能力,容易静默失败。
Q3:负载均衡怎么让会话不丢?
用ip_hash(按客户端 IP 固定)或hash ... consistent(一致性哈希);也可以把会话外置到 Redis。
Q4:backup和 keepalived 有什么区别?backup是后端服务器层面的主备(Nginx 内部选择);keepalived 是Nginx 自身的高可用(多台 Nginx + VIP),两者互补,通常一起用。
Q5:迁移到 Docker 后配置改了要重建镜像吗?
不需要。配置用 volume 挂载,改完nginx -t校验 +nginx -s reload即可。只有新增/修改编译模块时才需要重新 build 镜像。
Q6:透明代理容器一定要network_mode: host吗?
是。透明代理需要看到真实网卡与真实源 IP,桥接网络下TPROXY拿不到正确信息。
十、总结与最佳实践
把全文要点收拢成一张表:
| 需求 | 方案 | 关键配置 |
|---|---|---|
| 内网统一出口上网 | 正向代理 | resolver+proxy_pass http://$http_host...;HTTPS 用proxy_connect |
| 对外统一入口、SSL 卸载 | 反向代理 | proxy_pass+X-Forwarded-* |
| 客户端零配置、服务器也不感知 | 网络层透明代理 | TPROXY+proxy_bind $remote_addr transparent |
| 客户端零配置、实现简单 | 应用层透明代理 | REDIRECT+$http_host/ssl_preread |
| 多台后端分摊流量 | 负载均衡 | upstream+least_conn/ip_hash |
| 后端兜底 | 主备冗余 | server ... backup |
| Nginx 自身高可用 | keepalived | vrrp_instance+ VIP |
| 平滑上云/容器化 | Docker | volume 挂载配置 +network_mode: host(透明代理) |
几条血泪经验:
- 能用反向代理解决的,别硬上透明代理——后者维护成本极高。
- 透明代理的成败在于细节:内核模块、
rp_filter、策略路由、root 权限,缺一不可。 - 配置一定要进版本管理,Docker 化后配置与镜像解耦,回滚和协作都会轻松很多。
- 上线前先压测 + 准备回滚,尤其是
TPROXY这类改内核行为的方案。
参考资料
- Nginx 官方文档:https://nginx.org/en/docs/
ngx_http_proxy_connect_module(正向代理 CONNECT):https://github.com/chobits/ngx_http_proxy_connect_module- Nginx
proxy_bind指令(transparent参数):https://nginx.org/en/docs/stream/ngx_stream_proxy_module.html#proxy_bind - Nginx
upstream负载均衡:https://nginx.org/en/docs/http/ngx_http_upstream_module.html - keepalived 官方文档:https://www.keepalived.org/manpage.html
如果本文对你有帮助,欢迎点赞 👍 + 收藏 ⭐ + 关注,你的支持是我持续输出的最大动力!
遇到配置问题也欢迎在评论区留言,我会尽量一一解答。