How to Rewrite Absolute URLs in Nginx Reverse Proxy Responses for Third-Party Applications


1 views

When setting up an nginx reverse proxy for internal applications, one common issue arises when the backend application generates absolute URLs with its internal hostname. Consider this scenario:

# Internal inaccessible server
location = / {
proxy_pass https://internalserver:8080/;
}

The application might output HTML containing links like <a href="https://internalserver:8080/dashboard">, which obviously won't work for external users accessing through https://proxyserver/.

The most robust approach is using nginx's ngx_http_sub_module:

location / {
proxy_pass https://internalserver:8080/;
proxy_set_header Host $host;

sub_filter 'https://internalserver:8080' 'https://proxyserver';
sub_filter_once off; # Replace all occurrences
sub_filter_types *; # Process all content types
}

For more complex replacements, use regular expressions with the ngx_http_sub_module:

location / {
proxy_pass https://internalserver:8080/;

sub_filter 'https://internalserver:8080(.*)' 'https://proxyserver$1';
sub_filter_once off;
}

Modern web apps often make AJAX calls with hardcoded URLs. We need to handle these too:

# For JSON responses
location /api/ {
proxy_pass https://internalserver:8080/api/;

sub_filter '"url":"https://internalserver:8080' '"url":"https://proxyserver';
sub_filter_types application/json;
}

URL rewriting impacts performance. Consider these optimizations:

http {
proxy_buffers 16 32k;
proxy_buffer_size 64k;
sub_filter_max_match_size 32k;
}

For dynamic content manipulation, nginx+lua provides more flexibility:

location / {
proxy_pass https://internalserver:8080/;

header_filter_by_lua_block {
ngx.header.content_length = nil
}

body_filter_by_lua '
local resp = ngx.arg[1]
resp = resp:gsub("https://internalserver:8080", "https://proxyserver")
ngx.arg[1] = resp
';
}

  • Ensure sub_filter_types includes all needed MIME types
  • Verify the replacement strings exactly match the case in the original content
  • Check for hardcoded URLs in JavaScript files with different paths

When setting up an Nginx reverse proxy for internal applications, a common challenge arises with hardcoded absolute URLs. Consider this setup:

# Internal application (intranet-only)
https://internalserver:8080/

# Public-facing proxy
https://proxyserver/

The application works when accessed through the proxy, but all internal links point to https://internalserver:8080/ instead of the proxy URL, breaking navigation for external users.

The most robust approach uses Nginx's ngx_http_sub_module (included by default in most distributions):

server {
    listen 443 ssl;
    server_name proxyserver;

    location / {
        proxy_pass https://internalserver:8080;
        proxy_set_header Host $host;
        
        # Enable substitution
        sub_filter_once off;
        sub_filter_types text/html;
        sub_filter 'https://internalserver:8080' 'https://proxyserver';
        sub_filter 'http://internalserver:8080' 'https://proxyserver';
    }
}

For complex applications, you might need multiple substitution patterns:

location / {
    proxy_pass https://internalserver:8080;
    
    # Multiple substitution patterns
    sub_filter 'https://internalserver:8080' 'https://proxyserver';
    sub_filter 'href="/' 'href="/prefix/';
    sub_filter 'src="/' 'src="/prefix/';
    sub_filter 'action="/' 'action="/prefix/';
    sub_filter_once off;
    sub_filter_types *;
}

For dynamic applications that generate URLs in JavaScript, you may need additional processing:

location / {
    proxy_pass https://internalserver:8080;
    
    # Base tag injection for relative paths
    sub_filter '</head>' '<base href="https://proxyserver/"></head>';
    sub_filter_types text/html application/javascript;
    sub_filter_once off;
}

For maximum flexibility, consider using Nginx with Lua:

location / {
    proxy_pass https://internalserver:8080;
    
    header_filter_by_lua_block {
        ngx.header.content_length = nil
    }
    
    body_filter_by_lua_block {
        local body = ngx.arg[1]
        body = string.gsub(body, 'https?://internalserver:8080', 'https://proxyserver')
        ngx.arg[1] = body
    }
}

Remember that content modification impacts performance:

  • Enable gzip compression (gzip on)
  • Set appropriate buffer sizes (proxy_buffers)
  • Consider caching strategies for static content