How to Add Custom Headers to ProxyPass Requests in Apache


8 views

When working with Apache's reverse proxy configuration, you might need to inject custom headers into requests being forwarded to backend servers. A common use case is adding authentication tokens, tracking information, or other metadata that your backend service requires.

Here's the standard ProxyPass setup mentioned in the question:

<VirtualHost *:80>
  ServerName hello.local

  ProxyPass / http://localhost:8810/
  ProxyPassReverse / http://localhost:8810/
</VirtualHost>

Apache's mod_headers module combined with environment variables provides the most flexible solution:

<VirtualHost *:80>
  ServerName hello.local
  
  # Execute external command and store in environment variable
  SetEnvIfExpr "exec('/usr/bin/an_external_program')" MY_HEADER_VALUE
  
  # Pass the header to the backend
  RequestHeader set MyHeader "%{MY_HEADER_VALUE}e"
  
  ProxyPass / http://localhost:8810/
  ProxyPassReverse / http://localhost:8810/
</VirtualHost>

For more complex scenarios, you can use mod_rewrite:

<VirtualHost *:80>
  ServerName hello.local
  
  RewriteEngine On
  RewriteRule .* - [E=MY_HEADER_VALUE:exec('/usr/bin/an_external_program')]
  RequestHeader set MyHeader "%{MY_HEADER_VALUE}e"
  
  ProxyPass / http://localhost:8810/
  ProxyPassReverse / http://localhost:8810/
</VirtualHost>
  • Make sure mod_headers and mod_setenvif (or mod_rewrite) are loaded
  • The external program should execute quickly to avoid request delays
  • For security, validate the output of external commands
  • Consider caching the command output if it doesn't change frequently

To verify headers are being passed correctly:

curl -v http://hello.local

Or check the logs on your backend server to confirm the headers are received.


When working with Apache's reverse proxy configuration, there are cases where you need to inject dynamic headers into proxied requests. The standard ProxyPass directive doesn't directly support executing external commands to generate header values, which creates an interesting technical challenge.

While Apache's Header directive is powerful for manipulating response headers, it has limitations when:

  • You need to set request headers (not response headers)
  • The header value must come from an external program's output

Here's a complete implementation that solves both requirements:


  ServerName hello.local
  
  RewriteEngine On
  RewriteRule .* - [E=MY_HEADER_VALUE:/usr/bin/an_external_program]
  
  RequestHeader set MyHeader "%{MY_HEADER_VALUE}e"
  
  ProxyPass / http://localhost:8810/
  ProxyPassReverse / http://localhost:8810/

The key components are:

  1. mod_rewrite to execute the external command and store its output in an environment variable
  2. mod_headers with RequestHeader to set the proxied request header
  3. The %{VAR}e syntax to reference environment variables

For more complex scenarios where you need to process the command output:


  ServerName api.example.com
  
  # Capture command output and sanitize it
  RewriteEngine On
  RewriteRule .* - [E=RAW_VALUE:/usr/local/bin/generate-api-key --service=proxy]
  RewriteRule .* - [E=SANITIZED_KEY:%{RAW_VALUE}|base64]
  
  # Set multiple dynamic headers
  RequestHeader set X-API-Key "%{SANITIZED_KEY}e"
  RequestHeader set X-Request-ID "%{TIME_YEAR}%{TIME_MON}%{TIME_DAY}%{TIME_HOUR}%{TIME_MIN}%{TIME_SEC}"
  RequestHeader set X-Forwarded-Proto "https" env=HTTPS
  
  ProxyPass / http://backend:8080/
  ProxyPassReverse / http://backend:8080/

Be mindful that:

  • External command execution happens for every request
  • Consider caching mechanisms if the command output doesn't change frequently
  • For high-traffic sites, implement this at a load balancer level instead

For Apache 2.4+ with mod_lua enabled, you can achieve this programmatically:


  ServerName lua.example.com
  
  LuaHookFixups /path/to/header_script.lua add_headers
  
  ProxyPass / http://localhost:8888/

With header_script.lua containing:

function add_headers(r)
  local handle = io.popen("/usr/bin/dynamic-header-generator")
  local result = handle:read("*a")
  handle:close()
  r.headers_in["X-Dynamic-Header"] = result
  return apache2.OK
end