How to Properly Forward Custom Headers Through Nginx Proxy to Backend Servers


3 views

When working with Nginx as a reverse proxy, developers often encounter situations where custom headers from client requests don't make it to the backend servers. This is particularly frustrating when you're trying to pass authentication tokens, special API keys, or other custom metadata between services.

Looking at the example configuration:

location / {
    proxy_read_timeout 2000;
    proxy_next_upstream error;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_pass_header X_CUSTOM_HEADER;
    proxy_redirect off;
    proxy_max_temp_file_size 0;
    proxy_pass https://prod;
    break;
}

The key issue here is using proxy_pass_header which only works for response headers from the backend, not request headers from the client.

There are two correct approaches to forward custom headers:

Method 1: Using proxy_set_header

location / {
    # ... other directives ...
    proxy_set_header X_CUSTOM_HEADER $http_x_custom_header;
    proxy_pass https://prod;
}

The $http_ prefix is crucial here - Nginx automatically converts incoming headers to variables with this prefix and lowercase names.

Method 2: Wildcard forwarding

location / {
    # ... other directives ...
    proxy_pass_request_headers on;
    proxy_pass https://prod;
}

This approach forwards all headers, but might be less secure in some scenarios.

To verify headers are being forwarded:

curl -v -H "X_CUSTOM_HEADER: testvalue" http://yourdomain.com

Check both Nginx access logs and backend server logs to trace the header flow.

  • Header name case sensitivity (Nginx normalizes to lowercase)
  • Underscores in header names (may require underscores_in_headers on;)
  • Header size limitations (large_client_header_buffers)
http {
    underscores_in_headers on;
    large_client_header_buffers 4 32k;

    server {
        location / {
            proxy_set_header X-Custom-Header $http_x_custom_header;
            proxy_set_header X-Another-Header $http_x_another_header;
            proxy_pass http://backend;
        }
    }
}

This comprehensive configuration handles most edge cases while maintaining security.


When setting up Nginx as a reverse proxy, you might encounter situations where custom headers sent by clients aren't being passed through to your backend servers (like Apache/PHP). This behavior occurs because Nginx, by default, only forwards a specific set of headers to backend services.

# Problematic Nginx configuration example
location / {
    proxy_pass https://prod;
    proxy_set_header Host $http_host;
    # Missing custom header forwarding
}

To properly forward custom headers, you need to understand these key directives:

# Solution configuration
location / {
    proxy_pass https://prod;
    
    # Standard headers
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    
    # Custom header handling
    proxy_pass_header X_CUSTOM_HEADER;
    proxy_set_header X_CUSTOM_HEADER $http_x_custom_header;
    
    # Other important settings
    proxy_redirect off;
    proxy_read_timeout 2000;
}

The original configuration used proxy_pass_header alone, which isn't sufficient. You need both:

  1. proxy_pass_header - Allows passing specific headers from Nginx to client
  2. proxy_set_header - Forwards headers from client to backend

Here's a fully functional configuration that handles various header scenarios:

server {
    listen 80;
    server_name domain.com;
    
    location /api/ {
        proxy_pass https://backend_server;
        
        # Standard headers
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        
        # Custom headers
        proxy_pass_header X_CUSTOM_HEADER;
        proxy_set_header X_CUSTOM_HEADER $http_x_custom_header;
        
        # Additional headers example
        proxy_set_header X-API-Version $http_x_api_version;
        proxy_pass_header X-API-Secret;
        
        # Performance settings
        proxy_read_timeout 300;
        proxy_connect_timeout 300;
    }
}

Verify your setup with these curl commands:

# Test custom header forwarding
curl -v -H "X_CUSTOM_HEADER: test_value" http://domain.com/api/endpoint

# Test multiple headers
curl -v \
    -H "X_CUSTOM_HEADER: foo" \
    -H "X-API-Version: 2.1" \
    http://domain.com/api/test

For more complex scenarios, consider these approaches:

# Forward all headers (not recommended for production)
location / {
    proxy_pass http://backend;
    proxy_pass_request_headers on;
}

# Conditional header forwarding
map $http_x_custom_header $forward_custom_header {
    default $http_x_custom_header;
    ""      "";
}

location / {
    proxy_set_header X_CUSTOM_HEADER $forward_custom_header;
}
  • Header name case sensitivity (Nginx normalizes headers to lowercase)
  • Underscores in header names may require underscores_in_headers on
  • Headers exceeding buffer sizes need proxy_buffer_size adjustment
  • HTTPS backends may require additional proxy_set_header directives