282

Nginx is running on port 80, and I'm using it to reverse proxy URLs with path /foo to port 3200 this way:

location /foo {
                proxy_pass http://localhost:3200;
                proxy_redirect     off;
                proxy_set_header   Host $host;
}

This works fine, but I have an application on port 3200, for which I don't want the initial /foo to be sent to. That is - when I access http://localhost/foo/bar, I want only /bar to be the path as received by the app. So I tried adding this line to the location block above:

rewrite ^(.*)foo(.*)$ http://localhost:3200/$2 permanent;

This causes 302 redirect (change in URL), but I want 301. What should I do?

jeffreyveon
  • 2,985
  • 3
  • 14
  • 7
  • 3
    If you have any problem with Grafana case you should use these recipe: http://docs.grafana.org/installation/behind_proxy/#nginx-configuration-with-sub-path – mohsen saeedi Jan 15 '18 at 21:29

5 Answers5

292

Any redirect to localhost doesn't make sense from a remote system (e.g. client's Web browser). So the rewrite flags permanent (301) or redirect (302) are not usable in your case.

Please try following setup using a transparent rewrite rule:

location  /foo {
  rewrite /foo/(.*) /$1  break;
  proxy_pass         http://localhost:3200;
  proxy_redirect     off;
  proxy_set_header   Host $host;
}

Use curl -i to test your rewrites. A very subtle change to the rule can cause nginx to perform a redirect.

dldnh
  • 103
  • 4
Jens Bradler
  • 6,133
  • 2
  • 16
  • 13
  • 3
    The URL path still begins with /foo in my app when I do that... – jeffreyveon Apr 16 '12 at 01:48
  • There must be a different problem. I reproduced this scenario successfully, just minutes ago. Original URL: [http://development/foo/testme/1234](http://development/foo/testme/1234) - REQUEST_URI of a PHP script running on a Apache connected as proxy back-end: '/testme/1234' – Jens Bradler Apr 17 '12 at 08:32
  • 17
    The regex should probably be `/foo(.*)`, otherwise `example.com/foo` wont be matched. (which is probably what jeffreyveon experienced) – Benno Jan 30 '14 at 02:11
  • This kind of works, but my body i'm setting with proxy_set_body is being removed. – Justin Thomas Mar 23 '17 at 17:00
  • rewrite /(.*) /socket.io/ break; SAVE MY DAY FOR SOCKET.IO – user956584 Jul 25 '17 at 18:52
  • use `curl -i` to test. you may find, as I did, that a redirect is taking place if you're not careful with your rewrite. with a location of "/foo/" and a rewrite of "/foo/(.*) /$1" I kept rewrite from taking place. – dldnh Feb 11 '18 at 13:52
  • just noticed the original answer had /foo/ - editing. – dldnh Feb 11 '18 at 14:09
  • Please, can somebody explain me, what for using `break` as third argument in rewrite? – Ramzan Chasygov Dec 21 '19 at 18:38
  • Using "break" means that the rewriting process stops and the changed request will not be processed by another location. If you are using "last" the rewriting process stops as well, but the changed request will be handled again by Nginx to find a matching location directive. – Jens Bradler Dec 21 '19 at 21:06
  • 4
    If you also want to match `/foo` without the slash: `rewrite /foo(/.*|$) /$1 break;` – dols3m Jun 01 '21 at 21:39
240

Simple location prefix matching works for this without using a rewrite rule as long as you specify a URI in the proxy_pass directive:

location /foo {
  proxy_pass http://localhost:3200/;
}

Notice the additional / at the end of the proxy_pass directive. NGINX will strip the matched prefix /foo and pass the remainder to the backend server at the URI /. Therefore, http://myserver:80/foo/bar will post to the backend at http://localhost:3200/bar.

From the NGINX docs on proxy_pass:

If the proxy_pass directive is specified with a URI, then when a request is passed to the server, the part of a normalized request URI matching the location is replaced by a URI specified in the directive:

DanArl
  • 2,511
  • 1
  • 10
  • 6
  • 22
    Works for me than I've added / to location /foo/ { – Andrei N Nov 26 '15 at 10:41
  • 1
    While this works, the other answer is more self-documenting. This feels like magic. – Eric Rini Dec 06 '16 at 19:21
  • @Eric IMO the performance benefits far outweigh the readability downsides. this will be much, _much_ faster because it's simple replacement instead of invoking a full regexp engine. – strugee Jan 26 '17 at 21:05
  • As the proxy reverse is made in a clean way, if you have an app that complains about the `:port/` on it's current url (the dom code, not the document retrieved by the `proxy_pass`) will not totall work. Actuall faced an app like that and even if i removed every port mention except the bind port. So, i'll dig mor into the url rewrite ... cuz the answered question still not solving my problem or my problem is another. – m3nda Apr 12 '17 at 22:40
  • 2
    Took too long to realize the importance of keeping or removing trailing slash. – Parvez Aug 02 '17 at 09:25
  • 14
    This will actually pass `//xyz` to the host if you do that. – Archimedes Trajano Mar 06 '18 at 16:35
  • 1
    While this solution "seems" to win out on all these forums, it should be noted in the answer itself that Nginx will URL decode the URL and pass the decoded URL to the proxied server. So this solution will not work if your URL carries URL encoded parts. – coding May 08 '18 at 22:14
  • 1
    also note that this won't work in case you are using variables in your `proxy_pass` - see the linked nginx documentation that also states that! The accepted answer by @Jens Bradler worked though! – schneida Apr 01 '19 at 13:21
  • This is the right answer! In my case to proxy `/api/smth` to `/smth` I needed to add trailing slash for proxy_pass and for location too, – Konstantin Jan 18 '22 at 15:07
  • you need to add a trailing slash to your location also, otherwise it'll pass `//xyz`. Should be `location /foo/` – Duc Trung Mai Jul 22 '22 at 05:31
124

The absolute most correct way and best practice is usually as follows:

location /foo/ {
    proxy_pass http://localhost:3200/; # note the trailing slash!
}

  • Note the dire importance of the trailing slash in proxy_pass, which automatically alters the $uri variable to have the /foo/ on the front-end correspond with / on the backend. No need for an explicit rewrite directive.

  • Additionally, note that the the trailing / in the location is quite important as well — without it, you risk having weird-looking URLs on your site at one point (e.g., a working /fooen in addition to /foo/en).

    Additionally, the trailing / in the location with proxy_pass also ensures some special handling, as per the documentation of the location directive, to effectively cause an implicit location = /foo {return 301 /foo/;} as well.

    So, by defining a location with the trailing slash as above, you not only ensure that slash-less suffix URLs like /fooen won't be valid, but also that a /foo without a trailing slash will continue to work as well.


Reference documentation:

cnst
  • 12,948
  • 7
  • 51
  • 75
  • 1
    It looks like `$args` are lost: `http://frontend/foo?bar=baz` will be proxied to `http://backend/`. Notice that args are not the part of url – Vanuan Nov 01 '17 at 17:47
  • @Vanuan, are you sure about that? I'm pretty certain `$args` should still be handled appropriately if you use the code above, as they're separate from `$uri`, and should get assembled back in, unless you're using explicit variables in your `proxy_pass`. – cnst Nov 03 '17 at 06:53
  • @cnst Oh, I see. I'm using the host variable. That's counter intuitive. – Vanuan Nov 04 '17 at 18:08
  • @cnst What would you recommend if I need a mapping of upstream servers with proxy_pass directive? Like here https://serverfault.com/questions/562332/nginx-can-i-use-a-mapping-of-upstream-servers-with-proxy-pass-directive – Vanuan Nov 04 '17 at 18:10
  • Hm. It sounds like you need yet another proxy? – Vanuan Nov 04 '17 at 18:12
  • Yeah, if you're explicitly using `$uri` alone within `proxy_pass`, then of course that may present a problem with `$args` gone missing; there's `upstream`, `map` and many other directives available for doing mapping of upstream servers; does https://serverfault.com/a/881881/110020 help? – cnst Nov 05 '17 at 00:28
  • Using this approach will prevent passing `/foo` from going to the proxy so if there is a redirect to `/` because /foo is a directory then this will not work. – Archimedes Trajano Mar 07 '18 at 17:07
  • 1
    @ArchimedesTrajano, you are incorrect, as there's special handling for `/foo` to redirect to `/foo/`, so, unless you're doing something weird on the backend, even `/foo` requests will still work with the above code. (This is actually already part of the answer, BTW.) – cnst Mar 07 '18 at 17:20
  • Doesn't work with resolving the url on demand (so the host can be down while nginx is started). `resolver 127.0.0.11 valid=30s;`, `set $destiny http://localhost:3200/;` and `proxy_pass $destiny`. – luckydonald May 08 '18 at 10:50
  • 11
    While this solution "seems" to win out on all these forums, it should be noted in the answer itself that Nginx will URL decode the URL and pass the decoded URL to the proxied server. So this solution will not work if your URL carries URL encoded parts. – coding May 08 '18 at 22:15
  • 1
    This is the best answer and @coding is absolutely right. I didn't know about the decoding. Thanks for pointing that out. It's key information. You can use a rewrite rule to preserve url encoding, and achieve the same functionality (see link below). But if you don't know and you run into it, you'll be running around a while trying to figure out your problem. https://serverfault.com/questions/459369/disabling-url-decoding-in-nginx-proxy – Kendrick Taylor Feb 17 '19 at 07:15
2

try

location /foo {
    proxy_pass http://localhost:3200/;
    ....

or

location ^~ /foo {
    proxy_pass http://localhost:3200/;
    ....
DerGeh
  • 39
  • 1
1

@Terabuck Sorry for not replying no rep yet.

You should not use localhost because you are depending on the fact that application is running on a server with a hosts file. local host is only a default translation to 127.0.0.1. There is nothing stating that you must have this hosts files. It's just very common to have one.

Having a loopback interface is again another common thing to depend on but you are still dependent on the loopback interface on the networking stack. It's a rare case to not have these two. If you ever worry about this. At least on unix/linux you have the option for sockets. This will eliminate the need to for the network stack to reach the localhost. Use caution with this approach as there are a few factors that will come into place on the host OS. Such as the number of open files etc.

  • I would suggest that it's so incredibly common to have a way to map the name localhost to 127.0.0.1 that it's probably fine to do... – lindes Mar 17 '21 at 02:45