ProxyPreserveHost 必须设为 on 才能透传原始 Host 头,否则 Apache 会将其替换为后端地址(如 localhost:8080),导致后端无法识别用户访问的真实域名(如 api.example.com),引发 502、404 或 400 错误。

ProxyPreserveHost 为什么必须设为 on 才能透传原始 Host
默认情况下,Apache 的 mod_proxy 会把反向代理请求中的 Host 头替换成后端服务器地址(比如 localhost:8080),这会导致后端应用无法正确识别用户访问的原始域名(如 api.example.com)。ProxyPreserveHost on 的作用就是阻止这个替换行为,让原始 Host 头原样转发——这是实现多租户、虚拟主机或依赖域名做路由/鉴权的后端服务(如 Express、Django、Spring Boot)的前提。
常见错误现象:502 Bad Gateway 或后端返回 404 / 400,日志里看到后端收到的 Host 是 127.0.0.1 或 localhost,而不是用户实际访问的域名。
- 该指令只在启用代理时生效(即已加载
mod_proxy和对应协议模块,如mod_proxy_http) - 必须放在
块、或虚拟主机配置内,不能写在全局 Server 配置顶层 - 若同时用多个
ProxyPass,每个都需要单独配ProxyPreserveHost on,它不继承
典型配置位置和最小可用写法
最常出错的是把 ProxyPreserveHost 放错层级。它必须和 ProxyPass 在同一作用域,否则无效。以下是在虚拟主机中正确启用的最小配置:
<virtualhost>
ServerName api.example.com
ProxyPreserveHost on
ProxyPass / http://127.0.0.1:3000/
ProxyPassReverse / http://127.0.0.1:3000/
</virtualhost>
注意:ProxyPassReverse 不是可选的——它重写响应头里的 Location、Content-Location 等 URL,否则 302 跳转会暴露内部地址。
- 如果用
做路径级代理,ProxyPreserveHost on必须写在里面 - 在
.htaccess中无法使用该指令(属于服务器级指令,需要AllowOverride All也不行) - Apache 2.4+ 默认不启用
mod_proxy_http,需先执行a2enmod proxy_http
和 X-Forwarded-Host 冲突吗?要不要一起配
ProxyPreserveHost 和 X-Forwarded-Host 解决的是不同问题:前者改的是实际发送给后端的 Host 请求头;后者是额外添加一个自定义头,供后端读取原始域名。两者可以共存,但多数场景下只需前者。
是否要加 X-Forwarded-Host 取决于后端是否信任并解析它。例如 Node.js 的 express 默认不读这个头,得手动取 req.get('X-Forwarded-Host');而 Spring Boot 的 ForwardedHeaderFilter 默认会处理它。
- 如果后端只认
Host头(推荐方式),就只开ProxyPreserveHost on - 如果后端明确要求
X-Forwarded-Host,再加RequestHeader set X-Forwarded-Host %{HTTP_HOST}e - 不要同时开启又让后端优先读
X-Forwarded-Host,可能被伪造,有安全风险
验证是否生效的三个实操步骤
配置完别急着 reload,先确认是否真起作用。最直接的方式是让后端打印收到的 Host 头值,或者用 curl -v 抓包看请求头走向。
- 在后端加一行日志,输出
req.headers.host(Node)或request.getHeader("Host")(Java),访问后检查输出是否为api.example.com - 用
curl -v http://api.example.com/test,观察 Apache access log 是否记录了原始域名(而非 IP) - 临时在 Apache 配置里加
LogFormat "%{Host}i %h %l %u %t \"%r\" %>s %O" hostlog+CustomLog /var/log/apache2/host.log hostlog单独看 Host 字段
容易忽略的是 SELinux 或防火墙拦截了代理连接,导致请求根本没发到后端——此时 ProxyPreserveHost 再正确也没用。先确保 curl http://127.0.0.1:3000 在服务器本机能通,再查代理链路。











