87

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,025
  • 1
  • 8
  • 7

4 Answers4

79

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
  • 7,922
  • 31
  • 35
  • 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 '14 at 09:21
  • @robjohncox what exactly did you try? – Alexey Ten Apr 24 '14 at 09: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 '14 at 11:51
  • 8
    Can I upvote the "# note this slash" comment in this response? Three cheers for that comment! – 8one6 Jan 12 '16 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 05 '17 at 22:50
  • @mueslo Also note the trailing slash in the location block identifier (i.e., `/some/path/` _not_ `/some/path`. Adding that fixed my problem. – John K Nov 28 '18 at 03:18
67

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
  • 34,499
  • 3
  • 38
  • 58
  • 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 '14 at 09: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. – Tero Kilkanen Apr 27 '14 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. https://www.nginx.com/resources/wiki/start/topics/tutorials/config_pitfalls/#root-inside-location-block – guy mograbi Sep 27 '15 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. – Tero Kilkanen Jan 19 '16 at 16:59
  • This solution is nice but it breaks my virtual host configs in the target server as it receives the $host as name. Any idea of how change the current url and not going through this conflict? – pedrofs Aug 15 '16 at 20:21
  • 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? – Tero Kilkanen Aug 15 '16 at 21:30
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
  • 12,948
  • 7
  • 51
  • 75
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,639
  • 10
  • 26
  • 36
tomdunn
  • 21
  • 2