Nginx Redirect via Proxy, Rewrite and Preserve URL
In Nginx we have been trying to redirect a URL as follows:
http://example.com/some/path -> http://192.168.1.24
where the user still sees the original URL in their browser. Once the user is redirected, say they click on the link to /section/index.html
, we would want this to make a request that leads to the redirect
http://example.com/some/path/section/index.html -> http://192.168.1.24/section/index.html
and again still preserve the original URL.
Our attempts have involved various solutions using proxies and rewrite rules, and below shows the configuration that has brought us closest to a solution (note that this is the web server configuration for the example.com
web server). However, there are still two problems with this:
- It does not perform the rewrite properly, in that the request URL received by the web server
http://192.168.1.24
includes/some/path
and therefore fails to serve the required page. -
When you hover on a link once a page has been served,
/some/path
is missing from the URLserver { listen 80; server_name www.example.com; location /some/path/ { proxy_pass http://192.168.1.24; proxy_redirect http://www.example.com/some/path http://192.168.1.24; proxy_set_header Host $host; } location / { index index.html; root /var/www/example.com/htdocs; } }
We are looking for a solution that only involves changing the web server configuration on example.com
. We are able to change the config on 192.168.1.24
(also Nginx), however we want to try and avoid this because we will need to repeat this setup for hundreds of different servers whose access is proxied through example.com
.
Solution 1:
First, you shouldn't use root
directive inside the location block, it is a bad practice. In this case it doesn't matter though.
Try adding a second location block:
location ~ /some/path/(?<section>.+)/index.html {
proxy_pass http://192.168.1.24/$section/index.html;
proxy_set_header Host $host;
}
This captures the part after /some/path/ and before index.html to a $section variable, which is then used to set the proxy_pass destination. You can make the regex more specific if you require.
Solution 2:
You should use URI part in proxy_pass
directive.
Also, you mixed up order arguments of proxy_redirect
directive, and probably you don't need it at all. Nginx has reasonable default for this directive.
In this case your location
block could be really simple:
location /some/path/ {
proxy_pass http://192.168.1.24/;
# note this slash -----------^
proxy_set_header Host $host;
}
Solution 3:
You can use the following config to have a 100% seamless mapping between /some/path/
on the front-end and /
on the backend.
Note that this is the only answer so far that would also seamlessly take care of absolute paths generating 404 Not Found
errors, provided that the correct HTTP Referer
header is sent by the browser, so, all those gifs should continue to load without any need to modify the underlying HTML (which is not only expensive, but is also not supported without additional modules not compiled by default).
location /some/path/ {
proxy_pass http://192.168.1.24/; # note the trailing slash!
}
location / {
error_page 404 = @404;
return 404; # this would normally be `try_files` first
}
location @404 {
add_header Vary Referer; # sadly, no effect on 404
if ($http_referer ~ ://[^/]*(/some/path|/the/other)/) {
return 302 $1$uri;
}
return 404 "Not Found\n";
}
You can find the complete proof-of-concept and minimal-viable-product within the https://github.com/cnst/StackOverflow.cnst.nginx.conf repository.
Here's a testing run to confirm that all the edge cases seem to works:
curl -v -H 'Referer: http://example.su/some/path/page.html' localhost:6586/and/more.gif | & fgrep -e HTTP/ -e Referer -e Location
> GET /and/more.gif HTTP/1.1
> Referer: http://example.su/some/path/page.html
< HTTP/1.1 302 Moved Temporarily
< Location: http://localhost:6586/some/path/and/more.gif
< Vary: Referer
curl -v localhost:6586/and/more.gif | & fgrep -e HTTP/ -e Referer -e Location
> GET /and/more.gif HTTP/1.1
< HTTP/1.1 404 Not Found
curl -v localhost:6586/some/path/and/more.gif | & fgrep -e HTTP/ -e Referer -e Location -e uri
> GET /some/path/and/more.gif HTTP/1.1
< HTTP/1.1 200 OK
request_uri: /and/more.gif
P.S. If you have a lot of different paths to map, then instead of doing a regex comparison of $http_referer
within an if
within location @404
, you might want to use the global-based map
directive instead.
Also note that the trailing slashes in both the proxy_pass
, as well as the location
it is contained in, are quite important as per a related answer.
References:
- http://nginx.org/r/location
- http://nginx.org/r/error_page
- http://nginx.org/r/if
- http://nginx.org/r/return
- http://nginx.org/r/add_header
Solution 4:
When that slash is added to an nginx proxied jenkins, you are presented with the “It appears that your reverse proxy set up is broken" error.
proxy_pass http://localhost:8080/;
Remove this -----------------------------^
It should read
proxy_pass http://localhost:8080;