91

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 URL

    server {
        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.

robjohncox
  • 1,065

5 Answers5

81

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;
}
Alexey Ten
  • 9,247
68

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.

Tero Kilkanen
  • 38,887
6

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:

cnst
  • 14,646
2

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;
chicks
  • 3,915
  • 10
  • 29
  • 37
tomdunn
  • 21
1

We can simplify cnst's answer to this:

server {
    listen          80;
    server_name     www.example.com;
location /some/path/ {
    # As explained in the other answers, this is enough to make nginx
    # rewrite the request URLs.
    proxy_pass http://192.168.1.24/;
}

location / {
    # Redirect links, explained below
    if ($http_referer ~ ^http://www\.example\.com/some/path) {
        return 302 /some/path/$request_uri;
    }

    index index.html;
    root  /var/www/example.com/htdocs;
}

}

The issue with reverse proxying an unaware web server (192.168.1.24) to a subpath (/some/path/) is that nginx usually forwards all response payloads from the reverse proxied server unmodified, which often includes absolute URLs like /index.html. When a browser visits /some/path/, this example URL would then point to http://www.example.com/index.html, but the resource is only available at http://www.example.com/some/path/index.html!

There is, however, a way around this: The "Referer" header is sent by the browser to indicate to the server where the user is coming from. By matching it against our "proxy URL", we can redirect all requests made from /some/path/ to the correct URL.

if ($http_referer ~ ^http://www\.example\.com/some/path) {

~ is the case-sensitive regex operator, and because we're using regex here, we have to escape the dots with \. The ^ character at the beginning of the URL means that the string has to begin with the expression it precedes.

return 302 /some/path/$request_uri;

HTTP 302 means "Found" (originally "Temporarily moved"). This tells the browser: "Hey, I found what you requested, but it's actually at /some/path/[...]." The browser then makes the request to the correct URL.

Obviously, this solution assumes and necessitates that the web server at 192.168.1.24 does not know anything about the reverse proxy and does not prepend /some/path/ to its links. Otherwise, the redirection part is not needed. Those links might look like this: /abc, which would then get redirected by nginx to /some/path/abc, if the Referer header is set to http://www.example.com/some/path.

xieve
  • 11