Score:0

Using a part of the URI as a port number for nginx proxy_pass directive

cn flag

This is my nginx configuration :

server {
        listen 443 ssl;
        server_name sub.example.fr ;
        location ~ ^/ (123[0-9])$ { # regex work
                #rewrite ^/[0-9]{4}(.*)$ $1 last; # do not work, with last or break
                #proxy_pass http://localhost:$1/; # add slash not allow
                proxy_pass http://localhost:$1;
        }
}

I want to forward https://sub.example.fr/1234 to http://localhost:1234, so I just want to extract port number, remove it from url and use it into proxy_pass.

Score:2
gr flag

First of all, your ^/(123[0-9])$ regex will match only /1234 URI (or /1230, /1231, etc.) but not the /1234/some/path since you are using the $ end-of-string anchor. I'm assuming it isn't an error but a design solution. If it isn't, to match both /1234 and /1234/some/path (but not something like /12345), you can use an alternation: ^/(123[0-9])(?:/|$) (I'm using a non-capture group (?:...) here, as it is considered to have slightly better performance than the capture group).

Your configuration have a whole bunch of various mistakes. Lets look over each one.

Mistake #1: Incorrect rewrite ... last; usage.

Indeed, you can't specify an URI for the proxy_pass upstream like you are trying to do with a trailing slash

proxy_pass http://localhost:$1/; # add slash not allow

inside regex matching locations (as well as inside named locations) and the only way to change the URI that will be passed to the backend is to use the rewrite directive. However the right way to change an URI inside the location block to process it later within the same location is to use rewrite ... break; since rewrite ... last; will force nginx to search a new location for the rewritten URI. That means we need to use the break flag for the rewrite directive:

rewrite ^/[0-9]{4}(.*)$ $1 break;

Beware! No directives from the ngx_http_rewrite_module will be executed after rewrite ... break; (or simply break;) directive (update: after some testing, in opposite to what is said in the documentation, turns out it is true only for break directive but not the rewrite ... break one, at least for the OpenResty 1.17.8.2 based on nginx 17.8 core).

Mistake #2: You should enquote any string containing curly brackets or they will be considered as nginx configuration block.

Previous directive will give us the following error:

nginx: [emerg] directive "rewrite" is not terminated by ";"

To get rid of this error we need to enquote our regex pattern due to the curly brackets usage:

rewrite "^/[0-9]{4}(.*)$" $1 break;

Mistake #3: Numbered captures are overwritten whenever regular expression is evaluated.

Lets assume you have an URI of /1234:

location ~ ^/(123[0-9])$ {
    # Here the value of '$1' variable is "1234"
    rewrite "^/[0-9]{4}(.*)$" $1 break;
    # Here the value of '$1' variable is an empty string!
    proxy_pass http://localhost:$1; # There will be no port for 'proxy_pass' directive
}

You can save that $1 value before the rewrite rule evaluates its own regular expression using set directive:

location ~ ^/(123[0-9])$ {
    set $port $1;
    rewrite "^/[0-9]{4}(.*)$" $1 break;
    proxy_pass http://localhost:$port;
}

or better use the named capture group:

location ~ ^/(?<port>123[0-9])$ {
    rewrite "^/[0-9]{4}(.*)$" $1 break;
    proxy_pass http://localhost:$port;
}

Sometimes there can be no other solution but to use named captures, an excellent example of such a situation is given at this thread.

Mistake #4: Rewritten URI can't be an empty string.

Again, lets assume you have an URI of /1234. After the rewrite rule an internal nginx $uri variable will have an empty value. That would lead to HTTP 500 Internal Server error, and in nginx error log you'll see the following error message:

the rewritten URI has a zero length

To resolve that error just rewrite any URI to the /:

location ~ ^/(?<port>123[0-9])$ {
    rewrite ^ / break;
    proxy_pass http://localhost:$port;
}

If you'd need to serve any /<port_number>/some/path request like above, you should use the following location block to ensure your rewritten URI starts with a slash:

location ~ ^/(?<port>123[0-9])(?:/|$) {
    rewrite "^/\d{4}(?:/(.*))?" /$1 break;
    proxy_pass http://localhost:$port;
}

Mistake #5: You need to specify a resolver when using variables with the proxy_pass directive.

The above configuration is close to the working solution. However it is still non-workable, giving you HTTP 502 Bad Gateway error. You need to specify a resolver when you are using variables with the proxy_pass directive and your upstream is specified by the domain name rather than IP address, otherwise you'll get the following error in your nginx error log:

no resolver defined to resolve localhost

Generally you'd have to use something like

resolver 8.8.8.8;
location ~ ^/(?<port>123[0-9])$ {
    rewrite ^ / break;
    proxy_pass http://localhost:$port;
}

but since your upstream is localhost, you can specify its IP address instead of the domain name, this way you won't need that resolver directive at all:

location ~ ^/(?<port>123[0-9])$ {
    rewrite ^ / break;
    proxy_pass http://127.0.0.1:$port;
}

This configuration should be fully workable (at last). And once again, if you'd need to serve any /<port_number>/some/path request like above, you should use the following location block instead:

location ~ ^/(?<port>123[0-9])(?:/|$) {
    rewrite "^/\d{4}(?:/(.*))?" /$1 break;
    proxy_pass http://127.0.0.1:$port;
}
themadmax avatar
cn flag
Thank you very much, your answer is really impressive and very instructive, I hope it will help other people !
Ivan Shatsky avatar
gr flag
I made some (usefull, I hope) updates to the answer. Yes, the answer turned out to be quite detailed (isn't it worth to vote up? :) ), so to help other people find this information you can rename your question to something more common, like _Using a part of the URI as a port number for nginx proxy_pass directive_ or even more common _Using a part of the URI with nginx proxy_pass directive_.
us flag
One can also capture all the elements in `location` directive and leave out the `rewrite`.
mangohost

Post an answer

Most people don’t grasp that asking a lot of questions unlocks learning and improves interpersonal bonding. In Alison’s studies, for example, though people could accurately recall how many questions had been asked in their conversations, they didn’t intuit the link between questions and liking. Across four studies, in which participants were engaged in conversations themselves or read transcripts of others’ conversations, people tended not to realize that question asking would influence—or had influenced—the level of amity between the conversationalists.