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


18 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