How to Fix Apache RequestHeader Setting X-Forwarded-Proto to Null When Using SERVER_PROTOCOL Environment Variable


3 views

When configuring Apache as a load balancer, many developers need to set the X-Forwarded-Proto header to correctly pass protocol information to backend servers. A common approach is using:

RequestHeader set X-Forwarded-Proto "%{SERVER_PROTOCOL}e"

However, this often results in the header being set to null rather than the expected protocol (http/https).

The SERVER_PROTOCOL environment variable contains the full protocol string (e.g., "HTTP/1.1"), not just the protocol scheme. When Apache tries to use this with RequestHeader, it may fail to properly interpret the value.

Here are several reliable methods to set the X-Forwarded-Proto header:

1. Using HTTPS Environment Variable

RequestHeader set X-Forwarded-Proto "https" env=HTTPS
RequestHeader set X-Forwarded-Proto "http" env=!HTTPS

2. Using mod_rewrite

RewriteEngine On
RewriteCond %{HTTPS} =on
RewriteRule .* - [E=PROTO:https]
RewriteCond %{HTTPS} !=on
RewriteRule .* - [E=PROTO:http]
RequestHeader set X-Forwarded-Proto "%{PROTO}e"

3. Using SetEnvIf

SetEnvIf HTTPS "on" PROTO=https
SetEnvIf HTTPS "off" PROTO=http
RequestHeader set X-Forwarded-Proto "%{PROTO}e"

After making changes, test with:

curl -I http://yourdomain.com
curl -I https://yourdomain.com

Look for the X-Forwarded-Proto header in the response.

  • Ensure mod_headers is enabled: a2enmod headers
  • For SSL termination, place these directives in your virtual host configuration
  • Clear your browser cache when testing

When configuring Apache as a load balancer, many developers encounter an issue where:

RequestHeader set X-Forwarded-Proto "%{SERVER_PROTOCOL}e"

unexpectedly sets the header value to null instead of the expected protocol (http/https). This occurs because SERVER_PROTOCOL isn't available in the early request phase when mod_headers processes the RequestHeader directive.

Apache processes RequestHeader directives very early in the request lifecycle, before many server variables are populated. The environment variable expansion (%{VAR}e) syntax works, but only for variables that exist at this early stage.

Here's what's happening in the processing timeline:

  1. Request arrives at Apache
  2. mod_headers processes RequestHeader directives
  3. Other modules populate server variables
  4. Request gets forwarded to backend

Here are three reliable approaches to set X-Forwarded-Proto correctly:

Option 1: Use mod_setenvif with HTTPS

SetEnvIf X-Forwarded-Proto ^https$ HAVE_SSL=yes
RequestHeader set X-Forwarded-Proto "https" env=HAVE_SSL
RequestHeader set X-Forwarded-Proto "http"  env=!HAVE_SSL

Option 2: Use mod_rewrite (most reliable)

RewriteEngine On
RewriteCond %{HTTPS} =on
RewriteRule .* - [E=PROTO:https]
RewriteCond %{HTTPS} !=on
RewriteRule .* - [E=PROTO:http]
RequestHeader set X-Forwarded-Proto "%{PROTO}e"

Option 3: Direct HTTPS check

RequestHeader set X-Forwarded-Proto "https" env=HTTPS
RequestHeader set X-Forwarded-Proto "http"  env=!HTTPS

After making changes, verify with:

curl -I http://yourdomain.com
curl -Ik https://yourdomain.com

Look for the X-Forwarded-Proto header in responses. For more detailed debugging, enable Apache's log level:

LogLevel debug

The mod_rewrite solution adds minimal overhead as it only sets environment variables. All solutions are more efficient than using mod_php or other late-stage processors to set headers.

  • Not enabling required modules (a2enmod headers rewrite setenvif)
  • Placing directives in wrong configuration sections (should be in VirtualHost or server config)
  • Forgetting to restart Apache after changes

For complex load balancing setups, consider adding these additional headers:

RequestHeader set X-Forwarded-Port "%{SERVER_PORT}e"
RequestHeader set X-Forwarded-Host "%{HTTP_HOST}e"