Handle application URLs behind an Nginx path prefix
Map a reverse-proxied application under a subpath, fix redirect and cookie paths, and understand the limits of HTML substitution.
When an application is exposed at /app1/ but thinks it lives at /, assets, redirects, and cookies can use the wrong paths. Prefer configuring the application's external base path. If that is unavailable, treat request paths, redirect headers, response content, and cookie paths as separate problems.
Decide whether the backend receives the prefix
These are alternatives:
location /app1/ {
proxy_pass http://internal:8080/;
}
This maps /app1/x to /x. Without the trailing URI slash:
location /app1/ {
proxy_pass http://internal:8080;
}
The backend receives /app1/x. Choose the form matching the application's routing. Normalize the entry path when needed:
location = /app1 { return 301 /app1/$is_args$args; }
A browser resolves asset.css relative to the current document URL, while /asset.css starts at the host root. Nginx cannot infer the application's intended base just from a missing-file response.
Rewrite redirect headers when required
location /app1/ {
proxy_pass http://internal:8080/;
proxy_redirect http://internal:8080/ /app1/;
}
This handles an absolute upstream Location beginning with the specified origin. Inspect the actual header: a root-relative Location: /login is a different case. If the backend consistently uses that form, a targeted additional rule can be used:
proxy_redirect ~^(/(?!/).*)$ /app1$1;
The negative lookahead excludes network-path references such as //other.example/login. Test that already-prefixed redirects are not prefixed twice. proxy_redirect operates on Location and Refresh headers, not links inside HTML. Its default mapping often handles a straightforward literal proxy_pass mapping, but cannot be assumed to fix every redirect style.
Treat response substitution as a limited fallback
For a known HTML response format:
location /app1/ {
proxy_pass http://internal:8080/;
proxy_set_header Accept-Encoding "";
sub_filter 'href="/' 'href="/app1/';
sub_filter 'src="/' 'src="/app1/';
sub_filter_once off;
}
The sub module must be available. It processes text/html by default; additional types require explicit configuration. Clearing the upstream Accept-Encoding request header asks the backend for uncompressed text, but verify that it complies.
These literal replacements do not parse HTML, CSS, or JavaScript. They miss single-quoted attributes, CSS url(), dynamically assembled requests, and many other forms. They can also corrupt protocol-relative URLs such as //cdn.example.com/x.js. Expanding sub_filter_types does not make these patterns a general URL rewriter. Use substitution only after examining the actual response and testing all affected routes.
Scope cookies deliberately
proxy_cookie_path / /app1/;
This rewrites a cookie Path prefix. A cookie with Path / is already sent to /app1/; the reason to narrow it is scope, not to make it reach that path. Check existing cookie paths, Domain, Secure, and SameSite behavior. Cookie paths are not a security isolation boundary between applications on one origin.
Configure the application base path when possible
Framework settings such as Django's FORCE_SCRIPT_NAME, Spring's servlet context path, and frontend build base/public-path options can help. They have different semantics; confirm routing, static assets, generated links, and proxy headers together. Merely enabling one setting does not guarantee every plugin or absolute URL is fixed.
FAQ
Why did sub_filter do nothing?
Check module availability, content type, compression, and whether the exact literal pattern appears in the response.
Can it fix dynamically generated JavaScript URLs?
Not reliably. Configure the application's base URL or change its URL construction instead.
How should I verify the change?
Use the browser network panel to inspect failed assets and dynamic requests, then match those paths to proxy logs. Test redirects and login cookies as well as the page itself; a successful homepage is insufficient.
References: proxy_pass, proxy_redirect, and proxy_cookie_path, sub_filter.
Contact us
Join the community
to join the community
Try Guance
Start online and pay only for what you use.
Get startedChoose a Guance plan