Nginx Rewrite with Regex Multiple Token (+) - nginx

I'm having some issues with rewriting when it comes to multiple token numbers.
I'm working on versioning our API so deprecated version calls will go to our latest version. For the sake of simplicity, let's say we are supporting v4 and v5. When some hits our v1-3, we need it to go to v5. Likewise if someone hits v6, it should go to v5 as well.
Our nginx.conf uses proxy_pass with upstream so I have each version running on a different port
upstream v4 {
server 127.0.0.1:3000
}
upstream v5 {
server 127.0.0.1:3001
}
then we use location blocks to proxy
location ^~ /v5 {
proxy_pass $scheme://v5;
}
location ^~ /v4 {
proxy_pass $scheme://v4;
}
location ~* "^/v[0-9]+" {
rewrite ^/v[0-9]+/(.*)$ /$latestVersion/$1;
proxy_pass $scheme://$latestUpstream;
}
The last location block works for v0-9 but fails to catch when the number is multiple digits like v11, even though I'm using the +.
Any help on this would be great. Really confused by this. Thanks!

In the following block:
location ~* "^/v[0-9]+" {
rewrite ^/v[0-9]+/(.*)$ /$latestVersion/$1;
proxy_pass $scheme://$latestUpstream;
}
The rewrite statement performs an implicit rewrite ... last, which means that the URI is eventually processed by the location ^~ /v5 block, rendering the proxy_pass statement in this block, almost redundant. See this document for rewrite syntax.
The rewrite fails to match when the URI is presented as the version string alone without a trailing /, e.g. /v11.
You need to make the / following the version string optional. Try:
location ^~ /v {
rewrite ^/v[0-9]+(/.*)$ /$latestVersion$1 last;
}
The regular expression location block is probably unnecessary, so I have substituted a shorter prefix location (see this document for location syntax).
I have added a last suffix to make the intent of the rewrite clear.
EDIT: Responding to your comment
Distinguishing between the URIs /v5 and /v51 (i.e. without the trailing /) is problematic using prefix location blocks alone. If the URI always had (at minimum) a trailing / after the version string, it would be easy to solve by simply adding a trailing / to your two existing location blocks.
But supposing you need the short URIs like /v5 and /v51 to work, you should switch the solution to regular expression location blocks.
Be aware that regular expression location blocks are evaluated differently, and that order is important. You may need to move these location blocks before other regular expression location blocks in order to prevent them from being overridden by other code in your configuration file.
For example:
location ~* ^/v5(/|$) {
proxy_pass $scheme://v5;
}
location ~* ^/v4(/|$) {
proxy_pass $scheme://v4;
}
location ~* ^/v[0-9] {
rewrite ^/v[0-9]+(/.*)$ /$latestVersion$1 last;
}
Notice that the modifier is changed to ~* which introduces a regular expression (and also makes it case insensitive - which may or may not be important to you).

Related

Replicate proxy_pass location behavior with variables

So usually when creating a nginx location it would look something like this:
location /foo/ {
proxy_pass http://example.com/;
}
With this setup, requests to /foo/bar are forwarded to http://example.com/bar which is the intended behavior.
However when trying to prevent caching of the domain name example.com or when trying to prevent nginx from crashing if the upstream host is unavailable at startup the only solution seems to be to not use the target directly in the proxy_pass directive, but to instead create a variable containing the target like this:
location /foo/ {
set $targetUri http://example.com/;
proxy_pass $targetUri;
}
But this totally changes the setup. As soon as proxy_pass contains a variable, it no longer appends anything to the target uri, as the nginx docs describe:
When variables are used in proxy_pass [...]. In this case, if URI is specified in the directive, it is passed to the server as is, replacing the original request URI.
So requests to /foo/bar are simply forwarded to http://example.com/.
When bringing $request_uri into the mix, more than what we want is appended:
location /foo/ {
set $targetUri http://example.com$request_uri;
proxy_pass $targetUri;
}
Requests to /foo/bar are now forwarded to http://example.com/foo/bar.
The only workaround I have found is to resort to regex patterns for the location:
location ~ ^/foo/(.*)$ {
set $targetUri http://example.com/$1$is_args$args;
proxy_pass $targetUri;
}
Is there any way to replicate the behavior of proxy_pass when using variables without having to regex-match the location? The reason I want to avoid regex is because the location path is based on a user input from which the location block is generated.
Remove the trailing / from your $targetUri variable so that proxy_pass does not have the "optional URI" part in its value. Then use rewrite...break to duplicate the original behaviour.
For example:
location /foo/ {
set $targetUri http://example.com;
rewrite ^/foo(.*)$ $1 break;
proxy_pass $targetUri;
}

How does Nginx decide which location to handle requests if multiple locations match

I have nginx server with two locations:
location /service1 {
rewrite ^/service1/?(.*)$ /$1 break;
proxy_pass http://localhost:xxxx;
}
location ~* /service2(?<stuff>.*)$ {
rewrite ^ /service2$stuff break;
proxy_pass http://192.168.0.X;
}
Let's say I have a request http://hostname/service1/service2. I notice service2 always handle the request. But I want service 1 to handle it. How to set it?
It is important to understand how Nginx chooses which location block to handle requests. Please read https://www.digitalocean.com/community/tutorials/understanding-nginx-server-and-location-block-selection-algorithms, very helpful article.
Relevant excerpt from the article
It is important to understand that, by default, Nginx will serve regular expression matches in preference to prefix matches. However, it evaluates prefix locations first, allowing for the administer to override this tendency by specifying locations using the = and ^~ modifiers.
In your case you could use ‘location ^~ /service1’
Nginx location matcher - https://nginx.viraptor.info/

How to use regex like /page/.*/page/ in Nginx location

Url example:
http://test.com/test/page/4?-test?o?o_html/page/100/page/2/page/3/page/4/page/4/page/2/page/106/page/107/page/2/page/3/page/4/page/108/page/3/page/2/page/3/page/4&-test
I want to use nginx location to forbidden it.
But I faild, I have tried different rules in http://nginx.viraptor.info/
location ~ /page/.*/page/ {
return 403;
}
location ~* \/page/.*/page/ {
location ~* /page/\.*/page/ {
None of them worked...
I found only use /page/ is Worked.
location ~* /page/ {
But when I add .*/page/ like:
location ~* /page/.*/page/ {
It's not worked...
Now I use php to judge url like:
if (preg_match ("/\/page\/.*\/page\//i", $_SERVER["REQUEST_URI"]))
Please tell me how to use regex .* in nginx conf location. I want to use nginx.
Everything after the first ? are URI arguments, they are not part of the URI, as such a location match will never work. If there is a solution that allows you to do this in Nginx, it will be a kludge, you should perform this level of checking in your script like you stated you are already doing.
See: https://serverfault.com/questions/811912/can-nginx-location-blocks-match-a-url-query-string

Reverse image proxy without specifying host

I have the following in my config as a reverse proxy for images:
location ~ ^/image/(.+) {
proxy_pass http://example.com/$1;
}
The problem is that not all images will be example.com images and so we need to pass in the full url. If I try:
location ~ ^/image/(.+) {
proxy_pass $1;
}
I get an error:
invalid URL prefix in "https:/somethingelse.com/someimage.png"
The question is quite vague, but, based on the error message, what you're trying to do is perform a proxy_pass entirely based on the user input, by using the complete URL specified after the /image/ prefix of the URI.
Basically, this is a very bad idea, as you're opening yourself to become an open proxy. However, the reason it doesn't work as in the conf you supplied is due to URL normalisation, which, in your case, compacts http://example into http:/example (double slash becomes single), which is different in the context of proxy_pass.
If you don't care about security, you can just change merge_slashes from the default of on to off:
merge_slashes off;
location …
Another possibility is to somewhat related to nginx proxy_pass and URL decoding
location ~ ^/image/.+ {
rewrite ^ $request_uri;
rewrite ^/image/(.*) $1 break;
return 400;
proxy_pass $uri; # will result in an open-proxy, don't try at home
}
The proper solution would be to implement a whitelist, possibly with the help of map or even prefix-based location directives:
location ~ ^/image/(http):/(upload.example.org)/(.*) {
proxy_pass $1://$2/$3;
}
Do note that, as per the explanation in the begginning, the location above is subject to the merge_slash setting, so, it'll never have the double // by default, hence the need to add the double // manually at the proxy_pass stage.
I would use a map in this case
map $request_uri $proxied_url {
# if you don't care about domain and file extension
~*/image/(https?)://?(.*) $1://$2;
# if you want to limit file extension
~*/image/(https?)://?(.*\.(png|jpg|jpeg|ico))$ $1://$2;
# if you want to limit file extension and domain
~*/image/(https?)://?(abc\.xyz\.com/)(.*\.(png|jpg|jpeg|ico))$ $1://$2$3;
default "/404";
}
Then in your proxy pass part you would use something like below
location /image/ {
proxy_pass $proxied_url;
}
I have given three different example depending how you want to handle it

nginx 'proxy_pass' cannot have URI part in location?

I have a location block as
location #test{
proxy_pass http://localhost:5000/1;
}
but nginx complains that "proxy_pass cannot have URI part in location given by regular expression..." Does anyone know what might be wrong?
I'm trying to query localhost:5000/1 when an upload is complete:
location /upload_attachment {
upload_pass #test;
upload_store /tmp;
...
}
Technically just adding the URI should work, because it's documented here and it says that it should work, so
location #test{
proxy_pass http://localhost:5000/1/; # with a trailing slash
}
Should have worked fine, but since you said it didn't I suggested the other way around, the trick is that instead of passing /my/uri to localhost:5000/1, we pass /1/my/uri to localhost:5000,
That's what my rewrite did
rewrite ^ /1$1
Meaning rewrite the whole URL, prepend it with /1 then add the remaining, the whole block becomes
location #test{
rewrite ^ /1$1;
proxy_pass http://localhost:5000;
}
Note: #Fleshgrinder provided an answer explaining why the first method didn't work.
What's actually happening here?
nginx cannot process your desired URI part in the proxy_pass directive because you're within a named location (hence the error message). This is because nginx is built in a modular fashion and each configuration block is read in various stages by the various modules. So just remember that you cannot have a URI within your proxy_pass directive in the following cases:
Regular Expression Locations
Named Locations
if Blocks
How could we solve this problem?
Mohammad AbuShady explained how to do a rewrite and pass the requested URI to the proxy server. I just wanted to clarify the reason.
Try omitting the "/" (URI part) and check.
location #test{
proxy_pass http://localhost:5000;
}

Resources