When implementing reverse proxy configurations in Apache, many developers encounter scenarios where they need different ProxyPreserveHost behaviors for different backend targets. The standard ProxyPreserveHost directive applies globally, but sometimes we need more granular control.
Consider these common situations:
# For Varnish cache (needs original host header)
ProxyPass /cache http://localhost:6081
ProxyPassReverse /cache http://localhost:6081
# For external API (needs rewritten host header)
ProxyPass /api https://thirdparty.example.com
ProxyPassReverse /api https://thirdparty.example.com
Since ProxyPreserveHost can't be set per-proxy, we use RequestHeader directives:
<Location "/cache">
ProxyPass http://localhost:6081
ProxyPassReverse http://localhost:6081
RequestHeader set Host "%{HTTP_HOST}e"
</Location>
<Location "/api">
ProxyPass https://thirdparty.example.com
ProxyPassReverse https://thirdparty.example.com
# No Host header manipulation - uses backend host
</Location>
For more complex scenarios:
# Conditional header setting
<Location "/dynamic">
ProxyPass http://backend.example.com
RequestHeader set Host "%{HTTP_HOST}e" env=PRESERVE_HOST
RequestHeader unset Host env=!PRESERVE_HOST
</Location>
Verify your configuration with:
curl -v http://yourserver/cache/some-path
curl -v http://yourserver/api/endpoint
Check the Host header in your backend access logs to confirm proper behavior.
While this solution works, be aware that header manipulation adds minor overhead. For high-traffic sites, consider:
- Keeping the most frequent paths in the default configuration
- Using separate virtual hosts when possible
- Monitoring request processing times
When configuring Apache's reverse proxy with multiple backend services, we often encounter situations where different ProxyPass rules require different ProxyPreserveHost settings. The specific case involves:
- A local Varnish instance that needs original Host header preservation
- Third-party services that require Host header modification
By default, ProxyPreserveHost
is a server-wide directive that affects all ProxyPass rules uniformly. This becomes problematic when you need:
# This affects ALL ProxyPass rules
ProxyPreserveHost On
We can leverage Apache's <Location>
blocks to apply different ProxyPreserveHost settings:
<VirtualHost *:80>
ServerName example.com
# Default setting for most rules
ProxyPreserveHost Off
# Special case for Varnish
<Location /cached-content/>
ProxyPreserveHost On
ProxyPass http://localhost:6081/
ProxyPassReverse http://localhost:6081/
</Location>
# Third-party service
<Location /external-api/>
ProxyPreserveHost Off
ProxyPass http://thirdparty.example.com/
ProxyPassReverse http://thirdparty.example.com/
</Location>
</VirtualHost>
For more complex scenarios, we can use environment variables to control the behavior:
SetEnvIf Request_URI "^/cached-content/" PRESERVE_HOST=1
<IfDefine PRESERVE_HOST>
ProxyPreserveHost On
</IfDefine>
ProxyPass /cached-content/ http://localhost:6081/
ProxyPass /external-api/ http://thirdparty.example.com/
- Always include matching
ProxyPassReverse
directives - Test with
curl -v
to verify Host header behavior - Clear proxy cache when changing these settings
- Consider using
Header set Host "custom-value"
for more control
Here's a production-tested configuration:
<VirtualHost *:443>
SSLEngine on
# SSL certificates here...
# Default for all routes
ProxyPreserveHost Off
# Varnish cache layer
<LocationMatch "^/(products|images)/">
ProxyPreserveHost On
ProxyPass http://127.0.0.1:6081/ timeout=300 retry=0
ProxyPassReverse http://127.0.0.1:6081/
</LocationMatch>
# External payment gateway
<Location /checkout/payment>
ProxyPreserveHost Off
RequestHeader set Host "api.paymentprovider.com"
ProxyPass https://api.paymentprovider.com/v2/
ProxyPassReverse https://api.paymentprovider.com/v2/
</Location>
</VirtualHost>