Opened 9 years ago

Closed 9 years ago

Last modified 3 years ago

#786 closed defect (wontfix)

url decoding is senseless for proxy_pass

Reported by: aliquid@… Owned by:
Priority: minor Milestone:
Component: nginx-core Version: 1.8.x
Keywords: proxy_pass, url decoding Cc:
uname -a: FreeBSD Infra 9.3-RELEASE-p25 FreeBSD 9.3-RELEASE-p25 #0 r281084+d3a5bf7: Wed Sep 2 15:00:10 PDT 2015 amd64
nginx -V: nginx version: nginx/1.8.0
built with OpenSSL 0.9.8za-freebsd 5 Jun 2014
TLS SNI support enabled
configure arguments: --prefix=/usr/local/etc/nginx --with-cc-opt='-I /usr/local/include' --with-ld-opt='-L /usr/local/lib' --conf-path=/usr/local/etc/nginx/nginx.conf --sbin-path=/usr/local/sbin/nginx --pid-path=/var/run/ --error-log-path=/var/log/nginx-error.log --user=www --group=www --with-file-aio --http-client-body-temp-path=/var/tmp/nginx/client_body_temp --http-fastcgi-temp-path=/var/tmp/nginx/fastcgi_temp --http-proxy-temp-path=/var/tmp/nginx/proxy_temp --http-scgi-temp-path=/var/tmp/nginx/scgi_temp --http-uwsgi-temp-path=/var/tmp/nginx/uwsgi_temp --http-log-path=/var/log/nginx-access.log --with-http_gzip_static_module --with-http_gunzip_module --with-http_stub_status_module --with-pcre --with-http_ssl_module


I understand that URLs such as http://server/some%2Fpath is translated to http://server/some/path for security reasons. NginX is responsible for protecting NginX resources.

When configuring a stop-search URI for reverse proxy access, like

location ^~ /publicAppName/ {
  proxy_pass http://internal.local/; #trailing / to drop /publicAppName/
  proxy_redirect default;

then flow is locked onto target -- it will go to the internal.local server. Whatever application is running there is not NginX' responsibility. Dropping the trailing slash in proxy_pass URL forwards the URI part as-is, which is fine, it is upp to internal.local server to deal with any issues.

It does not make sense not to forward as-is the remaining URI (without /publicAppName/) when there is a trailing slash in proxy_pass URL.

Today, url decoding is enforced in this situation, possibly to "protect" internal.local server. Problem is if internal.local is running an application that is dependant on encoded urls provided by 3rd party tools (e.g. sinopia and npm), then there is no way to front this with an application prefix using NginX. Internal.local does not need protection, dropping one shoe for this is for the better.

Change History (6)

comment:1 by aliquid@…, 9 years ago

I'd be happy to provide working patch if there is a good chance of it reaching trunk in near time.

comment:2 by aliquid@…, 9 years ago

And for anybody passing by, I have found one or two workarounds for the sinopia+npm usecase that may be helpful;

  1. Specifying every possibility;
    location ^~ /sinopia/@scopea/ {
      proxy_pass http://sinopia.local:4873/@scopea%2F;
      proxy_redirect default;
    location ^~ /sinopia/-/readme/@scopea/ {
      proxy_pass http://sinopia.local:4873/-/readme/@scopea%2F;
      proxy_redirect default;
    location ^~ /sinopia/@scopeb/ {
      proxy_pass http://sinopia.local:4873/@scopeb%2F;
      proxy_redirect default;
    location ^~ /sinopia/-/readme/@scopeb/ {
      proxy_pass http://sinopia.local:4873/-/readme/@scopeb%2F;
      proxy_redirect default;
    ... repeat for every scopea, scopeb, ...
    location ^~ /sinopia/ {
      proxy_pass http://sinopia.local:4873/;
      proxy_redirect default;
  1. Possibly using PCRE and variables, which requires resolver and disables default proxy_redirect;
    location ^~ /sinopia(?<a>.*)@(?<b>.*)/(?<c>.*) {
      proxy_pass http://sinopia.local:4873$a@$b%2F$c;
      proxy_redirect http://sinopia.local:4873/ /sinopia/;
    location ^~ /sinopia/ {
      proxy_pass http://sinopia.local:4873/;
      proxy_redirect default;

Neither is pretty but keeps the show going.

Last edited 9 years ago by aliquid@… (previous) (diff)

comment:3 by Maxim Dounin, 9 years ago

Resolution: wontfix
Status: newclosed

When nginx is instructed to modify URI by using an URI component in proxy_pass to remove a location prefix, it will decode URI, remove matching location prefix, and encode modified URI. Decoding only a part of URI is hardly possible and not supported.

If you want to preserve URI exactly as it was sent by a client, use proxy_pass without an URI compontent, that is:

proxy_pass http://backend;

If you really want to modify an URI and at the same time preserve some particular parts of URI exactly as they were sent by a client, you can use proxy_pass with variables derived from the $request_uri variable. That is, something like this can be used assuming all valid requests always start with "/foo/" unescaped:

location /foo/ {
    set $modified_uri "/some/invalid/uri";

    if ($request_uri ~ ^/foo/(.*)$) {
        set $modified_uri $1;

    proxy_pass http://backend$modified_uri;

Note though that such approach is limited and won't work in some edge cases. E.g., it won't allow to properly handle internal redirects in nginx config, as well as various cases when some characters in the "/foo/" prefix are escaped.

comment:4 by Maxim Dounin, 7 years ago

See also #1281.

comment:5 by hkmaly@…, 7 years ago

For people who find this sooner than stack overflow: this solution seems best:

location /api/ {
        rewrite ^ $request_uri;
        rewrite ^/api/(.*) $1 break;
        return 400;

See for original author and details.

comment:6 by Maxim Dounin, 3 years ago

See also #2225.

Note: See TracTickets for help on using tickets.