89

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.

4 Answers 4

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.

6
  • 1
    Apologies for the late reply - this is so close to achieving what we are looking for. The only shortcoming is that, once the target page has been served, the URLs for links in the browser don't include '/some/path/' in them, meaning that they don't work if a user clicks on them. If we can work out how to overcome this I will update and accept this answer, as it's so nearly there.
    – robjohncox
    Apr 24, 2014 at 9:32
  • 9
    The links the browser see are generated by the software that is running on 192.168.1.24 server. You should modify that software in order to achieve what you want. Apr 27, 2014 at 18:18
  • 1
    not sure I follow your warning about root inside location block. reading nginx documentation it is the right way to do stuff. they only warn bad practice from not having a default root outside all locations. nginx.com/resources/wiki/start/topics/tutorials/config_pitfalls/… Sep 27, 2015 at 10:22
  • Well, it is easier to have a rule of thumb not to use root inside a location block, then you won't get any unexpected behavior for default locations. Only if you need to change the default root for each location, then you can use it. Jan 19, 2016 at 16:59
  • 1
    What do you mean by receives the $host as name? What is the exact HTTP header that was sent and what exactly you want it to send? Aug 15, 2016 at 21:30
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;
}
6
  • 1
    Apologies for the late reply - I tried this out and unfortunately it doesn't work for our use-case. The issue is that, when the request is made on the target server, the /some/path/ part of the URL is preserved in the request which is not a valid URL (we need to rewrite the URL as well to remove this).
    – robjohncox
    Apr 24, 2014 at 9:21
  • @robjohncox what exactly did you try?
    – Alexey Ten
    Apr 24, 2014 at 9:42
  • 11
    the slash did the trick for me. Now mydomain.com/some/path/* is proxied correctly to 192.168.1.24/* and not 192.168.1.24/some/path/*
    – Vad1mo
    Nov 18, 2014 at 11:51
  • 8
    Can I upvote the "# note this slash" comment in this response? Three cheers for that comment!
    – 8one6
    Jan 12, 2016 at 15:53
  • 1
    Not sure how this is working for all of you. This what I'm trying to achieve. However, when a user clicks a link that would redirect e.g. to 192.168.1.24/login on the local service, he is redirected to mydomain.com/login instead of mydomain.com/some/path/login
    – mueslo
    Sep 5, 2017 at 22:50
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:

1
  • what is | & and how does this work?
    – JohannesM
    Jun 17, 2021 at 11:09
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;
1
  • 1
    I don't think think this is what the OP's question was related to nor solves any of the issues mentioned. Aug 19, 2019 at 13:05

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .