How to Modify and Replace Response Headers in Nginx Reverse Proxy Configuration


12 views

When working with Domino server applications behind an Nginx reverse proxy, the X-XspLocation header presents a unique challenge. This header contains internal server URLs that need to be transformed before reaching the client.

The typical Nginx header manipulation pattern of proxy_hide_header followed by add_header doesn't work as expected because:

  • Nginx processes these directives in a specific order
  • The header modification occurs at a different phase of request processing
  • Multiple headers can end up being sent to the client

Here's the effective configuration that resolves this issue:

map $sent_http_x_xsplocation $xsplocation_new {
    default "";
    "~http://localhost:81/database.nsf/page.xsp/(.*)" "/$1";
}

server {
    # ... other server configuration ...

    location / {
        proxy_pass http://localhost:81/database.nsf/page.xsp/;
        
        # Remove original header
        proxy_hide_header X-XspLocation;
        
        # Add modified header only when the original exists
        if ($xsplocation_new) {
            add_header X-XspLocation $xsplocation_new;
        }
    }
}

Several important details make this solution work:

  1. The map directive should have a default value to avoid errors
  2. Conditional header addition prevents empty headers from being sent
  3. The regex pattern must exactly match your specific URL format

For more complex transformations, consider using Nginx JavaScript:

js_import /etc/nginx/conf.d/header_handler.js;

server {
    # ... other configuration ...
    
    location / {
        proxy_pass http://localhost:81/database.nsf/page.xsp/;
        js_header_filter header_handler.processHeaders;
    }
}

With header_handler.js containing:

function processHeaders(r) {
    const location = r.headersOut['X-XspLocation'];
    if (location) {
        const newLocation = location.replace(
            /http:\/\/localhost:81\/database.nsf\/page.xsp\/(.*)/,
            '/$1'
        );
        r.headersOut['X-XspLocation'] = newLocation;
    }
}

export default { processHeaders };

Always verify your header modifications using:

curl -I https://yourdomain.com/test-url

Or with more detailed inspection:

curl -vsk https://yourdomain.com/test-url 2>&1 | grep -i X-XspLocation

When implementing header transformations:

  • Map operations are generally faster than regex replacements
  • JavaScript processing adds overhead but offers more flexibility
  • Consider caching transformed values for frequently accessed URLs

If you encounter problems:

  1. Check Nginx error logs: tail -f /var/log/nginx/error.log
  2. Verify variable values using add_header Debug $variable_name
  3. Ensure there are no conflicting header modifications elsewhere in your config

When working with Domino servers behind Nginx reverse proxies, one particular pain point is handling the X-XspLocation header. This special header generated by XPages often contains full internal URLs that need to be rewritten before reaching the client.

The main issues we face:

Original header: http://localhost:81/database.nsf/page.xsp/ThankYou
Desired output: /ThankYou

Standard Nginx header manipulation techniques don't work well here because:

  • proxy_hide_header completely removes the header
  • add_header creates duplicate headers if the original isn't hidden
  • The map directive works but needs proper header replacement

The most reliable solution is to use Nginx's Headers More module. Here's the complete implementation:

load_module modules/ngx_http_headers_more_filter_module.so;

map $sent_http_x_xsplocation $xsplocation_new {
    "~http://localhost:81/database.nsf/page.xsp/(.*)" "/$1";
    default "";
}

server {
    # ... other server config
    
    location / {
        proxy_pass http://localhost:81/database.nsf/page.xsp/;
        
        # Header manipulation
        more_set_headers -s 200 'X-XspLocation: $xsplocation_new';
        more_clear_headers 'X-XspLocation';
    }
}

If you can't use additional modules, this approach works with standard Nginx:

map $sent_http_x_xsplocation $xsplocation_new {
    "~http://localhost:81/database.nsf/page.xsp/(.*)" "/$1";
    default "";
}

server {
    # ... other server config
    
    location / {
        proxy_pass http://localhost:81/database.nsf/page.xsp/;
        
        # Hide original and add new only when map returns value
        proxy_hide_header X-XspLocation;
        if ($xsplocation_new) {
            add_header X-XspLocation $xsplocation_new;
        }
    }
}

When implementing this solution:

  1. Ensure your map directive appears outside server blocks
  2. The regex pattern must match your specific Domino configuration
  3. Test with curl -I to verify header changes
  4. Clear browser cache when testing as 301/302 redirects get cached

To troubleshoot header issues, add these directives temporarily:

add_header X-Debug-Original-XspLocation $sent_http_x_xsplocation;
add_header X-Debug-New-XspLocation $xsplocation_new;

This will show you exactly what's happening with the header transformation.