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