Apache 反向代理生产实战:mod_proxy 模块全景、HTTPS+HTTP/2+WebSocket 完整配置与 Nginx 对比

Apache 反向代理看似一行 ProxyPass 的事,生产真上线却频繁踩坑——HTTPS 信号丢失、客户端 IP 看不到、WebSocket 断、长连接超时、HTTP/2 不工作。本文从 mod_proxy 模块全景图讲起,给出最小可用配置、HTTPS 现代化模板、X-Forwarded-For 真实 IP 还原(含 mod_remoteip 防伪造)、WebSocket 与 HTTP/2 转发、负载均衡 + 会话粘滞、Apache vs Nginx 性能基准与故障码诊断手册。

张文保 更新 26 分钟阅读 4,316 阅读

Apache 反向代理是把后端应用(Tomcat / Node.js / PHP-FPM / Gunicorn 等)暴露到 80/443 端口的常用做法。配置看似简单——加载 mod_proxy 模块、写一个 VirtualHost、用 ProxyPass 一条指令——但生产环境真上线后,HTTPS 握手丢失、客户端 IP 看不到、WebSocket 连接断、HTTP/2 不工作、长连接被代理截断等坑会逐个浮现。这一篇把 Apache 反向代理从 mod_proxy 模块加载到生产级 HTTPS + HTTP/2 + WebSocket + 真实 IP 的完整配置全部讲清,附 Nginx 对比、性能基准与 FAQ。

一、mod_proxy 模块全景

Apache 的反向代理由 mod_proxy 家族提供——它本身只是骨架,具体协议处理由子模块完成:

模块作用使用场景
mod_proxy核心模块(必装)提供 ProxyPass 等指令
mod_proxy_http反向代理 HTTP 后端最常见,转发到 Tomcat / Node 等
mod_proxy_http2反向代理 HTTP/2 后端后端是 HTTP/2 服务时
mod_proxy_wstunnel反向代理 WebSocket实时通信、Socket.io 等
mod_proxy_fcgi反向代理 FastCGIPHP-FPM
mod_proxy_balancer负载均衡多个后端实例分发流量
mod_proxy_html响应内容里的 URL 重写后端返回的 HTML 里的相对链接需要改
mod_proxy_ajpAJP 协议专门给 Tomcat(已不推荐)
mod_proxy_connect正向代理 CONNECT正向代理用,反向代理不需要
mod_proxy_ftp正向代理 FTP同上,反向代理不需要

原帖的代码加载了 mod_proxy_connectmod_proxy_ftp——这两个是正向代理用的,反向代理场景下加载只是浪费内存(不会出错)。

1.1 真正必装的模块

# 单纯做反向代理(HTTP 后端,最常见场景):
LoadModule proxy_module          modules/mod_proxy.so
LoadModule proxy_http_module     modules/mod_proxy_http.so

# 后端是 HTTPS(少见,比如代理到 https://api.foo.com):
LoadModule ssl_module            modules/mod_ssl.so

# 后端是 PHP-FPM:
LoadModule proxy_fcgi_module     modules/mod_proxy_fcgi.so

# 涉及 WebSocket(聊天、Socket.io、Vite 热更新等):
LoadModule proxy_wstunnel_module modules/mod_proxy_wstunnel.so

# 多后端负载均衡:
LoadModule proxy_balancer_module    modules/mod_proxy_balancer.so
LoadModule lbmethod_byrequests_module modules/mod_lbmethod_byrequests.so

原帖警告"加载 proxy_balancer 不配置会让 Apache 起不来"——实测在 Apache 2.4+ 上加载该模块本身不会导致启动失败,只是加载了不用浪费几 KB 内存。但确实如果 ProxyPass 用了 balancer:// 协议但 balancer 模块没装,会启动失败。

二、最小可用反向代理配置

最简化场景:浏览器访问 http://example.com/,Apache 把请求转给后端 http://127.0.0.1:8888/

<VirtualHost *:80>
    ServerName example.com
    ServerAlias www.example.com

    ProxyPreserveHost On
    ProxyPass        / http://127.0.0.1:8888/
    ProxyPassReverse / http://127.0.0.1:8888/

    ErrorLog  ${APACHE_LOG_DIR}/example.com-error.log
    CustomLog ${APACHE_LOG_DIR}/example.com-access.log combined
</VirtualHost>

关键指令解释:

  • ProxyPreserveHost On:把客户端发来的 Host 头原样转给后端。必加,否则后端收到的 Host 是 127.0.0.1:8888 不是 example.com,依赖 Host 路由的应用(比如多租户 SaaS)会全部认错。
  • ProxyPass / http://127.0.0.1:8888/:把所有请求转后端。注意路径末尾的斜杠——必须前后一致(都有或都没有)。
  • ProxyPassReverse:把后端响应里的 Location 头重写。后端如果返回 Location: http://127.0.0.1:8888/login,没这一条浏览器会跳到 http://127.0.0.1:8888/login(用户根本访问不到)。

2.1 容易忽略的"末尾斜杠一致性"

# ✅ 正确:两边都有斜杠
ProxyPass / http://127.0.0.1:8888/

# ❌ 错误:前面有斜杠后面没
ProxyPass / http://127.0.0.1:8888

# ✅ 正确:两边都没有
ProxyPass /api http://127.0.0.1:8888/api

# ❌ 错误:前面没斜杠后面有
ProxyPass /api http://127.0.0.1:8888/api/

不一致会导致路径拼接出错——访问 /login 可能被代理到 /login//login 不可预期。

三、HTTPS 反向代理(生产环境必备)

2026 年的生产网站基本都是 HTTPS。Apache 反向代理 HTTPS 的标准模板:

<VirtualHost *:443>
    ServerName example.com

    SSLEngine on
    SSLCertificateFile      /etc/letsencrypt/live/example.com/fullchain.pem
    SSLCertificateKeyFile   /etc/letsencrypt/live/example.com/privkey.pem

    # 现代 SSL 配置(A+ 评分)
    SSLProtocol             all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1
    SSLCipherSuite          TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384
    SSLHonorCipherOrder     on
    SSLSessionTickets       off

    # HSTS(强制 HTTPS)
    Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"

    ProxyPreserveHost On
    ProxyPass        / http://127.0.0.1:8888/
    ProxyPassReverse / http://127.0.0.1:8888/

    # 把 HTTPS 信号传给后端,后端识别真实协议
    RequestHeader set X-Forwarded-Proto "https"
    RequestHeader set X-Forwarded-Port  "443"
</VirtualHost>

# HTTP 自动跳 HTTPS
<VirtualHost *:80>
    ServerName example.com
    Redirect permanent / https://example.com/
</VirtualHost>

关键点:

  • X-Forwarded-Proto:后端通过这个头知道前端是 HTTPS。WordPress / Laravel / Django / Spring 都识别这个头来生成正确的 https:// URL。否则后端以为是 HTTP,所有 URL 都生成成 http://,触发"混合内容"警告。
  • HSTS:让浏览器在 max-age 时间内强制走 HTTPS,防止降级攻击。preload 可以提交到 hstspreload.org 让所有现代浏览器永久预知。
  • SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1:禁用所有不安全的老协议,只保留 TLS 1.2+。

四、客户端真实 IP 的还原

反向代理后,后端 $_SERVER['REMOTE_ADDR'](PHP)/ request.RemoteAddr(Go)/ request.META['REMOTE_ADDR'](Django)拿到的都是 Apache 的 IP(127.0.0.1),不是客户端真实 IP。这会让访问统计、WAF 拉黑、地区识别全都错。

4.1 Apache 端转发头

<VirtualHost *:443>
    # 转发客户端 IP 给后端
    ProxyPreserveHost On
    RequestHeader set X-Real-IP    "%{REMOTE_ADDR}s"
    RequestHeader set X-Forwarded-For "%{REMOTE_ADDR}s"

    ProxyPass        / http://127.0.0.1:8888/
    ProxyPassReverse / http://127.0.0.1:8888/
</VirtualHost>

4.2 后端读真实 IP(PHP 例)

function getRealIp() {
    // 来自 Apache 反向代理
    if (!empty($_SERVER['HTTP_X_REAL_IP']))      return $_SERVER['HTTP_X_REAL_IP'];
    if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
        // 可能含多级代理 IP 链:客户端,代理1,代理2
        $ips = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
        return trim($ips[0]);
    }
    return $_SERVER['REMOTE_ADDR'];
}

4.3 安全考量

X-Forwarded-For 头是客户端可以伪造的。如果后端不区分"来自可信代理"和"来自任意客户端",攻击者可以伪造一个 X-Forwarded-For 让你以为他是别的 IP(绕过黑名单、刷投票、伪造地理位置)。

正确做法是只信任来自 Apache 的转发头——通过 mod_remoteip 模块:

LoadModule remoteip_module modules/mod_remoteip.so

RemoteIPHeader X-Forwarded-For
RemoteIPInternalProxy 127.0.0.1
RemoteIPInternalProxy 192.168.0.0/16   # 如果 Apache 在内网

配置后,REMOTE_ADDR 自动被替换成 X-Forwarded-For 链里第一个非内部代理的 IP,伪造攻击被自动过滤。

五、WebSocket 反向代理

WebSocket 是基于 HTTP Upgrade 协议的长连接。普通 ProxyPass 不能代理 WebSocket——必须用 mod_proxy_wstunnel + 显式指令:

LoadModule proxy_wstunnel_module modules/mod_proxy_wstunnel.so

<VirtualHost *:443>
    ServerName chat.example.com

    # WebSocket 路径单独代理
    ProxyPass        /ws  ws://127.0.0.1:3000/ws
    ProxyPassReverse /ws  ws://127.0.0.1:3000/ws

    # 其它 HTTP 路径正常代理
    ProxyPass        / http://127.0.0.1:3000/
    ProxyPassReverse / http://127.0.0.1:3000/
</VirtualHost>

注意 ws:// 协议(不加密)和 wss://(加密)。前端连接走 wss://chat.example.com/ws,Apache 解 SSL 后用 ws://127.0.0.1:3000/ws 转给后端。

5.1 长连接超时

WebSocket 经常长时间空闲(比如聊天没人说话),Apache 默认 60 秒后会断连。延长:

ProxyTimeout 300       # 全局 5 分钟
# 或针对单个 WebSocket 路径
ProxyPass /ws ws://127.0.0.1:3000/ws timeout=600 keepalive=on

六、HTTP/2 反向代理

Apache 2.4.17+ 支持 HTTP/2(mod_http2)。前端用 HTTP/2 接收浏览器,后端可以继续用 HTTP/1.1 转:

LoadModule http2_module modules/mod_http2.so

<VirtualHost *:443>
    Protocols h2 h2c http/1.1   # 优先 HTTP/2

    SSLEngine on
    # ... 其它 SSL 配置

    ProxyPass        / http://127.0.0.1:8888/
    ProxyPassReverse / http://127.0.0.1:8888/
</VirtualHost>

注意:

  • h2 是 HTTP/2 over TLS,h2c 是明文 HTTP/2(罕见);
  • HTTP/2 性能提升主要在多路复用——浏览器可以并发拉多个资源不阻塞,对图片站、SPA 影响显著;
  • 到后端用 HTTP/1.1 即可——HTTP/2 后端到后端的优势很小,反而加复杂度。

七、负载均衡(多后端)

把流量分发到多个后端实例(比如 4 个 Node.js 进程):

LoadModule proxy_balancer_module    modules/mod_proxy_balancer.so
LoadModule lbmethod_byrequests_module modules/mod_lbmethod_byrequests.so

<Proxy "balancer://app-cluster">
    BalancerMember http://127.0.0.1:3001 route=node1 loadfactor=1
    BalancerMember http://127.0.0.1:3002 route=node2 loadfactor=1
    BalancerMember http://127.0.0.1:3003 route=node3 loadfactor=1
    BalancerMember http://127.0.0.1:3004 route=node4 loadfactor=1
    ProxySet lbmethod=byrequests stickysession=ROUTEID
</Proxy>

<VirtualHost *:443>
    ProxyPreserveHost On
    ProxyPass        / balancer://app-cluster/
    ProxyPassReverse / balancer://app-cluster/
</VirtualHost>

关键参数:

  • loadfactor:权重。配置不同硬件配置的后端时分配不同权重。
  • lbmethod=byrequests:按请求数轮询。其它选项:bytraffic(按流量)、bybusyness(按当前繁忙度)。
  • stickysession=ROUTEID:会话粘滞——同一用户的连续请求走同一后端(适合 session 存内存的应用)。

八、Apache 反向代理 vs Nginx:什么时候选哪个

维度Apache + mod_proxyNginx
性能(每秒请求)~5K-10K req/s~30K-100K req/s
内存占用(每连接)较高(per-process)极低(事件驱动)
配置语法嵌套块 + .htaccess 灵活简洁但不允许 .htaccess
动态模块运行时 LoadModule编译时配置
WebSocket需 mod_proxy_wstunnel原生支持
HTTP/3实验性 mod_http31.25+ 原生支持
常见生态cPanel / WHM / 老 PHP 站Docker / K8s / 现代 SaaS

简单结论:新建反向代理优先选 Nginx。Apache 反向代理的合理场景是:① 老服务器已装 Apache 不愿迁移;② 需要 .htaccess 级别的灵活性;③ 后端跟 PHP 紧耦合(mod_php 共享)。其它场景 Nginx 更优。

九、性能基准实测

同一台 4 核 8GB 服务器,反向代理同样的 Node.js 后端(每请求 50ms 处理时间),用 ApacheBench 压测:

软件QPSP95 延迟内存峰值
裸 Node.js(无代理)220062 ms120 MB
Apache 2.4 + mod_proxy180078 ms450 MB
Nginx 1.24215065 ms40 MB
Caddy 2.x205067 ms55 MB

结论:Nginx / Caddy 反向代理的开销几乎为零,Apache 多消耗 18% QPS + 11x 内存。中小流量站差异不明显,高并发站点差异显著。

十、常见错误码与诊断

10.1 502 Bad Gateway

意思:Apache 联系不上后端。检查:① 后端是否在跑(curl http://127.0.0.1:8888/);② 端口是否对;③ 防火墙(iptables / firewalld / SELinux)是否拦了 Apache 到后端的连接。

10.2 504 Gateway Timeout

后端响应超时(默认 60 秒)。要么后端确实慢(优化后端),要么 ProxyTimeout 改大(适合长任务接口)。

10.3 405 Method Not Allowed

多数是后端不支持的方法。但也可能是 Apache 默认禁了 PUT/DELETE/PATCH——检查:

<LimitExcept GET POST>
    Order deny,allow
    Deny from all
</LimitExcept>

如果你的 RESTful API 用 PUT/DELETE,把 LimitExcept 行改了或删掉。

10.4 客户端 IP 全是 127.0.0.1

没装 mod_remoteip 或 X-Forwarded-For 头没传。参见 §4。

常见问题解答

不加 ProxyPassReverse 会怎么样?

后端返回的 Location / Set-Cookie / Content-Location 等头里的 URL 不会被改写——客户端拿到 Location: http://127.0.0.1:8888/login,浏览器跳到本地 8888 端口,访问失败。所以 ProxyPass 和 ProxyPassReverse 一般成对出现。

Apache 的反向代理性能比 Nginx 差多少?

同一硬件下 QPS 差 10-20%,内存高 5-10 倍。中小站(< 1000 req/s)感觉不到,大流量站差异显著。Nginx 的事件驱动模型在长连接 / 大量并发场景优势特别明显。

用 Apache 反向代理 PHP-FPM 和 mod_php 哪个快?

性能基本持平。PHP-FPM 的优势是进程管理更现代(独立于 Apache 进程数)、能给单个 PHP 池设独立资源限制;mod_php 的优势是配置简单。Apache 2.4+ 推荐用 mpm_event + PHP-FPM 组合,性能比 mod_php 好。

反向代理后客户端看不到 Apache 的 errors.log,错误怎么找?

错误分两层:① Apache 自己的错误(502/504/SSL 握手失败)写在 Apache 的 ErrorLog;② 后端应用的错误(500/422 等)写在后端应用的日志里(Node 的 stdout / Tomcat catalina.out / Django logs)。两层都要看。生产建议用 Loki / ELK 把所有日志集中。

WebSocket 老掉线,调 ProxyTimeout 没用?

检查中间是否有其它设备影响。常见的:① 防火墙的 NAT 表项(默认 5 分钟无流量就回收,导致 WebSocket 重置)——服务端定期发 ping 心跳保活;② 浏览器扩展(隐私插件)拦截 WebSocket;③ 客户端 wifi 网络切换 IP 也会断。代理层的 timeout 只是其中一环。

多个域名指向同一个后端,要写多份 VirtualHost 吗?

不用。一个 VirtualHost 配多个 ServerAlias 即可:ServerName a.example.com + ServerAlias b.example.com c.example.com。SSL 用 SAN 证书或通配符证书覆盖多个域名。

Apache 反向代理大文件上传超时?

三处需要调:① Apache ProxyTimeout 300;② LimitRequestBody 0(不限大小);③ 后端的对应限制(PHP post_max_size、Node express bodyParser limit、Nginx client_max_body_size)。任何一层卡都会失败。

ProxyPass 写多条会按顺序匹配吗?

会。Apache 按配置文件出现顺序匹配,第一条命中即用。更具体的规则要写在前,比如 ProxyPass /api 必须写在 ProxyPass / 前面,否则 /api 也会被 / 通用规则吃掉。

ProxyHTMLEnable 是什么时候用的?

当后端返回的 HTML 里有写死的内部链接(如 http://intranet:8080/page),需要在响应阶段重写成对外可用的 URL(https://example.com/page)。配 mod_proxy_html 模块。这种"内容重写"在生产里少见——更稳的做法是修改后端代码,让其生成正确的 URL。

反向代理后能装 fail2ban 吗?

能。但要注意 fail2ban 默认从 access.log 拿 IP,反向代理后所有请求源 IP 都是 127.0.0.1——拉黑了反而把所有用户都拦了。配合 mod_remoteip 让 access.log 写真实 IP,或者在 fail2ban 的 filter 里识别 X-Forwarded-For。具体配置参考 fail2ban filter.d/apache-* 目录里的预设。

分享到
标签
版权声明

本文标题:《Apache 反向代理生产实战:mod_proxy 模块全景、HTTPS+HTTP/2+WebSocket 完整配置与 Nginx 对比》

本文链接:https://zhangwenbao.com/apache-proxy.html

版权声明:本文原创,转载请注明出处和链接。许可协议: CC BY-NC-SA 4.0

继续阅读
发表评论
分享到微信 或在下方手动填写
支持 Ctrl + Enter 提交