Nginx Nested Location Priority - nginx

Here is part of my nginx configuration:
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php7.4-fpm.sock;
}
location /wp-content/uploads/ {
location ~ .(aspx|php|jsp|cgi)$ { return 410; }
}
As I understand it, the order of priority in location blocks goes like this:
= (exact match) --> ^~ (preferential prefix) --> ~ (regex) or ~* (case-insensitive regex) --> (prefix - no special character)
I put a PHP file in /wp-content/uploads. I got a 410 response code (which is what I want). But I don't understand why the first location block didn't capture the request, since regex blocks take precedence over prefix.
Furthermore, when two regex locations match, the first declared regex location match takes the request. Yet the latter processes the request here.
Why am I getting the 410 response code for /wp-content/uploads/info.php?!?

I found the answer. There's a little-known secret of nginx location matching.
Here is the order of precedence, according to artfulrobot.uk:
1. Exact string matches location = /foo
2. The longest of any location ^~ ... matches
3. The first regex match that is nested within the single longest matching prefix match!
4. The first other regex match location ~ regex
5. The longest prefix match location /foo
Everybody knows about #1, #2, #4, and #5.
But #3 is the gotcha. That's what is happening here.
You'll want to read the article as he very granularly describes this seemingly undocumented and strange behavior of nginx.

Related

How to set wildcard reverse proxy in Nginx

Suppose you have a request for the following json file.
e.g. https://example.com/aaa/bbb/ccc.json
I want to have Nginx handle 'aaa' as a fixed string, and 'bbb' and 'ccc' as changing strings.
In this case, I want the proxy to work ignoring the 'bbb' path in the URL.
How should I describe this in a lcoation block in Nginx?
location /aaa {
# I want to ignore 'bbb' path and return ccc.json. 'ccc' varies.
alias /mydirectory/aaa;
try_files $url;
}
Locations in nginx can be defined with regular expressions (docs).
Depending on how specific you'd like to be, you might choose ~ for case-sensitive URI matching or ~* for case-insensitive URI matching. ^~ is also available as a prefix match, but note a match here will stop the engine from performing additional regex matching. Caveat emptor.
Something like this would be a relatively straightforward way to "absorb" the bbb (or any other combo of 1 or more letters, upper and lower case):
location ~ ^\/aaa\/[a-zA-Z]+ {
...
}
In order to capture the ccc.json, you'll want to add a capturing group as well:
location ~ ^\/aaa\/[a-zA-Z]+\/([\w\.]+) {
return 200 '$1';
}
Bonus: if you're using PCRE 7+ (docs), you can name your capture:
location ~ ^\/aaa\/[a-zA-Z]+\/(?<filename>[\w\.]+) {
return 200 '$filename';
}

Use script instead of regex for Nginx prox_redirect to modify Location header

For Nginx, proxy_redirect, can we use a string instead of string or regex? I have a need to replace multiple occurrences of text in the Location string using proxy_redirect and I am not sure how to do it with regex. Therefore, I am checking if the proxy_redirect can make us of script, then it could be easy.
You can use multiple proxy_redirect statements within a single block containing the proxy_pass statement. Nginx evaluates statements containing regular expressions in order until a match is found, so place the more specific regular expressions before the less specific ones.
To replace a single occurrence of a pattern within the Location header of a 3xx response, you would use:
proxy_redirect ~^(.*)origin.example.com(.*)$ $1main.example.com$2;
To replace two occurrences of the same pattern, you would use:
proxy_redirect ~^(.*)origin.example.com(.*)origin.example.com(.*)$ $1main.example.com$2main.example.com$3;
To replace one, two or three occurrences of the same pattern, you would use:
proxy_redirect ~^(.*)origin.example.com(.*)origin.example.com(.*)origin.example.com(.*)$ $1main.example.com$2main.example.com$3main.example.com$4;
proxy_redirect ~^(.*)origin.example.com(.*)origin.example.com(.*)$ $1main.example.com$2main.example.com$3;
proxy_redirect ~^(.*)origin.example.com(.*)$ $1main.example.com$2;
Use ~* to make the regular expression case-insensitive. See this document for details.

nginx escaping (+) sign in rewrite rule

I am trying to redirect some hits based on the image name in my Nginx configuration.
This is how it looks now:
location / {
rewrite ^/static-v3/(.*)/creditcard_sslseals_public.png https://somenewurl.com/credit-card-seals.png permanent;
rewrite ^/static-v3/(.*)/creditcard+sslseals_public.png https://somenewurl.com/credit-card-seals.png permanent;
# First attempt to serve request as file, then
# as directory, then fall back to displaying a 404.
try_files $uri $uri/ =404;
}
So the first condition with the filename creditcard_sslseals_public.png works properly, but the second is not working since there is a + sign in the image name like creditcard+sslseals_public.png, so I am getting a 404.
How can I escape the + in the second condition, but keep the regex before ^/static-v3/(.*)?
The + has a special meaning in a regular expression and needs to be escaped.
One option is to use a backslash to escape the character:
^/static-v3/(.*)/creditcard\+sslseals_public.png
Alternatively, create a character class containing just one character:
^/static-v3/(.*)/creditcard[+]sslseals_public.png

nginx location match rule, without matching trailing characters

I have the following rule:
location /foo {
Which matches well for the following examples:
mydomain.com/foo
mydomain.com/foo/
mydomain.com/foo/bar?example=true
However it is also matching for
mydomain.com/foobar
I don't want it to match to that last one (/foobar), it should only match if there is either nothing after the foo, or a slash and zero or more characters after it. I've tried location /foo/ { but that does not produce desired results either.
Can anyone shed some light on how to do this?
There are two ways to handle this, use a regular expression location block - or just handle /foo separately from /foo/.
Regular expression location blocks have a different evaluation order and are less efficient than prefix location blocks, so my preferred solution is the exact match location and prefix location.
Generally, /foo just redirects to /foo/, for example:
location = /foo {
return 302 /foo/;
}
location /foo/ {
...
}
See this document for more.
You can create one Location rule to /foobar whether you need it as an exception:
location = /foobar {
....
}
Nginx match first URI by = operator.

nginx get root based on url regex with some calculation rules

Given url like this: media/images/293_84072edb91d2b62387f529e2c4456c85f4dadee5
I wanna get path with following rules:
if
location ~* /media/images/\d*_(?<hash>[a-z0-9]{40})
then
take var $hash (84072edb91d2b62387f529e2c4456c85f4dadee5), get first char of it ('8'), for example let's name it with $hash[0]
then second and third chars together ('40'), named by $hash[1:3]
and then root where nginx can take the image must look like this one:
media/images/$hash[1]/$hash[1:3]/$hash
root -> media/images/8/40/84072edb91d2b62387f529e2c4456c85f4dadee5
How can I write this rule? Please, help me to understand.
location ~ ^/media/images/\d*_(?<hash0>[a-z0-9])(?<hash13>[a-z0-9]{2})(?<hash>[a-z0-9]{37})$ {
alias /server/path/$hash0/$hash13/$hash0$hash13$hash;
}
BTW, in pure nginx you can't work with string as in programming language. Nginx has only regexp.
But regexp can get any part of string as group. In your example 40 symbols of hash captured as named group. But you can easy create 2 capture groups: one with 1 symbol, second - with next 39 symbols. Or, can create 3 capture groups: with 1 symbol ((?<hash0>[a-z0-9]) in mine example), with next 2 symbols ((?<hash13>[a-z0-9]{2})) and with next 37 symbols ((?<hash>[a-z0-9]{37})). All groups has own name. Now we can create path using this captures.
Btw, name of groups are not required, this example can be written as
location ~ ^/media/images/\d*_([a-z0-9])([a-z0-9]{2})([a-z0-9]{37})$ {
alias /server/path/$1/$2/$1$2$3;
}
Named group important if you have few different regexp (in location, in server name, in if etc).
Now, why alias and not root. Root - it's sever root for this location. If location /a/ and root /home/www/ - filepath of /a/test.txt will be /home/www/a/test.txt. Alias replace current location, so if alias /home/www/ - filepath of /a/test.txt will be /home/www/test.txt.
So use root if your location and file structure the same, and alias - if location do not map to file system path directly.

Resources