Nginx pass_proxy subdirectory without url decoding

Solution 1:

But there is no easy way to fix this nginx behaviour. There are some bugs in nginx trac, you could add yours. trac.nginx.org/nginx/…. So, I think that the simplest way is to have subdomain. – Alexey Ten Feb 24 '15 at 14:49

https://trac.nginx.org/nginx/ticket/727

If you want nginx to do something custom, you can do so using ​proxy_pass with variables (and the $request_uri variable, which contains original unescaped request URI as sent by a client). In this case it will be your responsibility to do correct URI transformations. Note though that this can easily cause security issues and should be done with care.

Challenge accepted!

    location /api/ {
        rewrite ^ $request_uri;
        rewrite ^/api/(.*) $1 break;
        return 400;
        proxy_pass http://127.0.0.1:82/$uri;
    }

That's it, folks!


Here's for the full proof.

The config file for nginx/1.2.1:

server {
    listen 81;
    #first, the solution
    location /api/ {
        rewrite ^ $request_uri;
        rewrite ^/api/(.*) $1 break;
        return 400; #if the second rewrite won't match
        proxy_pass http://127.0.0.1:82/$uri;
    }
    #next, a few control groups
    location /dec/ {
        proxy_pass http://127.0.0.1:82/;
    }
    location /mec/ {
        rewrite ^/mec(/.*) $1 break;
        proxy_pass http://127.0.0.1:82;
    }
    location /nod/ {
        proxy_pass http://127.0.0.1:82;
    }
}

server {
    listen 82;
    return 200 $request_uri\n;
}

Here are the results of running the queries for each location:

% echo localhost:81/{api,dec,mec,nod}/save/http%3A%2F%2Fexample.com | xargs -n1 curl
/save/http%3A%2F%2Fexample.com
/save/http:/example.com
/save/http:/example.com
/nod/save/http%3A%2F%2Fexample.com
%

Note that having that extra return 400; is quite important — otherwise, you risk having a security issue (file access through //api etc), as Maxim has briefly mentioned in your trac ticket.


P.S. If you think using the rewrite engine as a finite-state automaton is super cool, you might also want check out my http://mdoc.su/ project, or fork it github.

Solution 2:

What you have to do is fairly easy as long as we are talking prefix matching with ^~ or no modifier

location /api/ {
  # if you don't want to pass /api/ add a trailing slash to the proxy_pass
  proxy_pass http://localhost:8080/;

  ...
}

And everything will be passed along without decoding, you don't have to pass $uri

Also while you use proxy pass you should also set these headers

# pass headers and body along
proxy_pass_request_headers on;
proxy_pass_request_body on;

# set some headers to make sure the reverse proxy is passing along everything necessary
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;