How do I make a regex nginx location work? - regex

I have the following locations in my nginx config:
server {
listen 80;
server_name localhost;
location ~ ^/(?!api)(.*)/api {
alias /var/www/api/$1;
}
location /api {
alias /var/www/api/latest;
}
I am trying to match /api as the latest version, and /<version>/api as anything else. The non regex location is working fine, but I am getting a 403 on the other location.
I don't think it is anything to do with the file and permissions that are being served, as I get a 403 if I try to access
/latest/api
even though these are the same files that are served by
/api
Does anyone have an ideas about why I am getting a 403?
The nginx error is:
directory index of "/var/www/api/latest" is forbidden, client:
172.17.0.1, server: localhost, request: "HEAD /latest/api/ HTTP/1.1",
host: "localhost"

The problem is not with the regular expression, but with the use of the alias directive within a regular expression location. See this document for more.
On a related note, rather than using a negative lookahead assertion, you should use the ^~ modifier on the prefix location. See this document for more.
For example:
location ~ ^(/[^/]+)/api(.*)$ {
alias /var/www/api$1$2;
}
location ^~ /api {
alias /var/www/api/latest;
}

Related

Nginx internal location ignores django completely and allows free access instead

I want to have a private media folder on my django website, accessible only to logged in users, so I got to know that I should handle authentication part on the django side, and file serving on the nginx side. However following internal location config examples I find it impossible to make it work. Nginx ignores django completely (only for the internal location case). Even if I don't have the url allowed in my urls.py and I have it listed as internal location in nginx, it will still be freely accessible to everybody.
I am posting my nginx configuration in hope that someone can find a mistake in it.
My expectation is that everything in /internal/ folder will not be accessible to anonymous users and it will only be accessible by the django application through X-Accel-Redirect header. Right now if I go to /internal/test.png in an incognito window it will show me the picture.
I am not posting my django code for now, since it is ignored anyway by nginx, so it must be the nginx config problem.
server {
server_name XXX.XX.XX.XXX example.com www.example.com;
location = /favicon.ico {
access_log off;
log_not_found off;
alias /home/user/myproject/static/favicon4.ico;
}
location /static/ {
root /home/user/myproject;
}
location /media/ {
root /home/user/myproject;
}
location / {
include proxy_params;
proxy_pass http://unix:/run/gunicorn.sock;
}
location /internal/ {
internal;
root /home/user/myproject;
}
root /home/user/myproject;
location ~* \.(jpg|jpeg|png|webp|ico|gif)$ {
expires 30d;
}
location ~* \.(css|js|pdf)$ {
expires 1d;
}
client_max_body_size 10M;
# below in this server block is only my Certbot stuff
}
P.S. I swapped identifiable data to X characters and basic names.
I had 2 more problems in this config and I will show everything I did to make it work. The original problem why nginx was ignoring django was in how nginx chooses which location block to use, as suggested by Richard Smith.
From nginx.org we can read:
To find location matching a given request, nginx first checks locations defined using the prefix strings (prefix locations). Among them, the location with the longest matching prefix is selected and remembered. Then regular expressions are checked, in the order of their appearance in the configuration file. The search of regular expressions terminates on the first match, and the corresponding configuration is used. If no match with a regular expression is found then the configuration of the prefix location remembered earlier is used.
And also:
If the longest matching prefix location has the “^~” modifier then regular expressions are not checked.
So regular expressions, if available, will be chosen first. ^~ modifier before prefix makes it chosen instead of regular expressions.
I changed location /internal/ { line to location ^~ /internal/ { and then I got 404 errors every time and no matter how I tried to access the files, but at least I knew nginx was going to this location.
The 2nd mistake was thinking that I can get away with using the same url as the folder name, or in other words, that I can put in my urls.py
path('internal/<path>', views.internal_media, name='internal_media')
together with
location ^~ /internal/ {
internal;
root /home/user/myproject;
}
in my nginx config.
I can't. The url must be different, because otherwise the url doesn't lead to django urls.py - it still leads to /internal/ location through nginx (again, due to how nginx chooses locations).
I changed my urls.py line to point to private url instead:
path('private/<path>', views.internal_media, name='internal_media')
and in the views.py file I redirect to /internal/:
def internal_media(request, path):
if request.user.groups.filter(name='team-special').exists():
response = HttpResponse()
response['X-Accel-Redirect'] = '/internal/' + path
del response['Content-Type'] # without this your images will open as text
return response
else:
raise PermissionDenied()
Aaaand this still didn't work. 404 errors every time. The 3rd mistake was forgetting about the combo of those two:
location / {
include proxy_params;
proxy_pass http://unix:/run/gunicorn.sock;
}
location ~* \.(jpg|jpeg|png|webp|ico|gif)$ {
expires 30d;
}
Now if I went to the url /private/test.jpg nginx didn't let me go to django, because location / is lower in priority than regular expressions, so location ~* took precedence and I never got to django. I noticed it by accident after a lot of time being frustrated, when I put the url incorrectly in incognito mode. When I went to /private/test.jp now I got a 403 forbidden error instead of 404.
It started working immediately when I commented out this.
location ~* \.(jpg|jpeg|png|webp|ico|gif)$ {
expires 30d;
}
location ~* \.(css|js|pdf)$ {
expires 1d;
}
So now internal files worked nicely, but I didn't have caching...
To fix that, I modified my /static/ and /media/ locations, but maybe I won't go into that here, since it is a different topic. I'll just post my full nginx config that works :)
Well, what you might want to also know is that:
~* tells nginx that we are writing a regular expression that is case insensitive
~ would tell nginx that we were writing a regular expression that is case sensitive
server {
server_name XXX.XX.XX.XXX example.com www.example.com;
location = /favicon.ico {
access_log off;
log_not_found off;
alias /home/user/myproject/static/favicon4.ico;
expires 30d;
}
location /static/ {
root /home/user/myproject;
expires 30d;
}
location /media/ {
root /home/user/myproject;
expires 30d;
}
location ~* \/(static|media)\/\S*\.(css|js|pdf) {
root /home/user/myproject;
expires 1d;
}
location ^~ /internal/ {
root /home/user/myproject;
internal;
expires 1d;
}
location / {
include proxy_params;
proxy_pass http://unix:/run/gunicorn.sock;
}
client_max_body_size 10M;
# certbot stuff
}

How to redirect nginx location to different rule?

I'm using NGINX and trying to get all request that has subdirectory FusionChart goes to special place, my intention is all url with [ROOT_URL]/FusionChart/ should go to # Rule 3 below.
However, I have an existing nginx rules stated that all static content should go to # Rule 2.
Nginx configuration:
server {
listen 80;
server_name domain2.com www.domain2.com;
access_log logs/domain2.access.log main;
# Rule 1
location ~ ^/(images|javascript|js|css|flash|media|static)/
{
root /var/www/virtual/big.server.com/htdocs;
expires 30d;
}
# Rule 2
location ~* \.(js|css|png|jpg|jpeg|gif|jqGrid|images|common|ico|map|woff|woff2|ttf|html)$ {
root /home/rcp/dev/public/others;
expires 10y;
}
# Rule 3
location ~ ^/(FusionCharts)/ {
root /home/rcp/dev/public/charts;
expires 10y;
}
location / {
proxy_pass http://127.0.0.1:8080;
}
}
The Tested URL:
http://domain2.com/FusionCharts/index.html
This will fall to # Rule 2, how do I modify the rules so that the request above landed in # Rule 3?
Regex matching locations are checked from first to last, the first found match is used for request processing. You can either swap second and third locations or use location ^~ { ... } syntax (check the documentation for details):
location ^~ /FusionCharts/ {
root /home/rcp/dev/public/charts/;
expires 10y;
}
Please note that with above location index.html file for /FusionCharts/index.html request will be searched under /home/rcp/dev/public/charts/FusionCharts directory. If it isn't a desired behavior, use alias /home/rcp/dev/public/charts/; directive instead of root one.

Nginx regex to exclude certain paths

I am trying to exclude some paths in my nginx proxypass and want everything else to go to my proxypass.
i.e I dont want to give proxy_pass to any url which starts with 'tiny' or 'static', but want everythign else to go to my proxypass location.
and I am using following regex to achieve this:
~ ^((?!tiny|static).)*$
But I always get 404 error.
If I navigate to following url in browser
localhost:8080/xyz
I want it to go to
localhost:8000/api/tiny/records/xyz
Can someone please help me in pointing out what is the issue ?
Here is my full nginx conf file:-
server {
listen 8080;
server_name localhost;
location ~ ^((?!tiny|static).)*$ {
proxy_pass http://localhost:8000/api/tiny/records/$1;
}
location / {
proxy_pass http://localhost:8000;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
Thanks a lot.
You are missing a / and have the * in the wrong place. The regular expression should be:
^(/(?!tiny|static).*)$
But you do not need to use a regular expression with a negative lookahead assertion. Instead, place a normal regular expression on the other location block.
For example:
location / {
proxy_pass http://localhost:8000/api/tiny/records/;
}
location ~ ^/(tiny|static) {
proxy_pass http://localhost:8000;
}

nginx invalid number of arguments in "proxy_pass" directive

nginx: [emerg] invalid number of arguments in "proxy_pass" directive in /etc/nginx/sites-enabled/django_direct:12
My nginx conf file:
server {
listen 80;
server_name 94.154.13.214;
location = /favicon.ico { access_log off; log_not_found off; }
location /static/ {
root /root/django_direct/main_app/;
}
location / {
include proxy_params;
proxy_pass unix: /root/django_direct/django_direct.sock;
}
}
What do I do?
UPD:
I have changed file like this:
proxy_pass http://unix:/root/django_direct/django_direct.sock;
But didn't help, I've restarted nginx and now
I am getting now a 502 Bad Gateway error.
If someone else finds this and has this error. Check that you have a semi-colon at the end of the parameters line. goes for all of the lines, not just proxy_pass.
Well the nginx sees two parameters: unix, and /root/django_redirect/.... I have the idea however that you want to specify a UNIX domain socket path. You can do this with:
proxy_pass http://unix:/root/django_direct/django_direct.sock;
As is described in the documentation.
Your argument is wrong.
It needs an URL:
Sets the protocol and address of a proxied server and an optional URI to which a location should be mapped. As a protocol, “http” or “https” can be specified. The address can be specified as a domain name or IP address, and an optional port:
proxy_pass http://localhost:8000/uri/;
or as a UNIX-domain socket path specified after the word “unix” and enclosed in colons:
proxy_pass http://unix:/tmp/backend.socket:/uri/;
See the documentation: http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass
I can also happen when you mistakenly put a = character in, trying to assign a value that way.
proxy_pass = http://other_node;
The same thing might happen to you with for example auth_basic = "restricted site".
Just remove the =.

NginX - Regular expression "/img/.*$" doesn't match "http://localhost:8000/img/20140313-08-35-02-108466000.png"

I'm using NginX 1.4.6 on Windows, and there's a server block (the only server block) in my nginx.conf which is defined as below:
server {
listen 8000;
server_name localhost;
access_log logs/host.access.log main;
location / {
root D:\Git\SNHAutomationRuby\output\screenshots;
}
location ~* /img/.*$ {
root D:\Git\SNHAutomationRuby\output\screenshots;
}
}
I have some png screenshots in folder D:\Git\SNHAutomationRuby\output\screenshots, and with this configuration, the image
D:\Git\SNHAutomationRuby\output\screenshots\20140313-08-35-02-108466000.png
can be successfully loaded in Firefox by accessing
http://localhost:8000/20140313-08-35-02-108466000.png
However, nginx retuened a 404 when I access
http://localhost:8000/img/20140313-08-35-02-108466000.png
I assume that there's something wrong in the location block
location ~* /img/.*$ {
root D:\Git\SNHAutomationRuby\output\screenshots;
}
Is the regular expression /img/.*$ incorrect so that it cannot match http://localhost:8000/img/20140313-08-35-02-108466000.png? Or is there any other configuration I've done incorrectly?
After taking a look at the error.log, I see what was happening now.
The error is:
2014/03/14 09:52:03 [error] 1756#1312: *1 CreateFile() "D:\Git\SNHAutomationRuby\output\screenshots/img/20140313-08-35-02-108466000.png" failed (3: The system cannot find the path specified), client: 127.0.0.1, server: localhost, request: "GET /img/20140313-08-35-02-108466000.png HTTP/1.1", host: "localhost:8000"
I realized that I should replace "\" in the root specification with "/", like:
location ~* /img/(.+)$ {
root D:/Git/SNHAutomationRuby/output/screenshots;
}
But it still doesn't work. The error in error.log is:
2014/03/14 13:15:00 [error] 5144#3480: *1 CreateFile() "D:/Git/SNHAutomationRuby/output/screenshots/img/AccountLevelAttendeeList_ColumnHeaders-527-20140313-10-03-48-904461000.png" failed (3: The system cannot find the path specified), client: 127.0.0.1, server: localhost, request: "GET /img/AccountLevelAttendeeList_ColumnHeaders-527-20140313-10-03-48-904461000.png HTTP/1.1", host: "localhost:8000"
Well, that png is located at D:/Git/SNHAutomationRuby/output/screenshots/ but not D:/Git/SNHAutomationRuby/output/screenshots/img/ so I got this error. Then I give it a simple fix with a redirect, like:
location ~* /img/(.+)$ {
return 301 ../$1;
}
It works!
So, I didn't write an incorrect regular expression but an invalid path specification, besides, I should use a redirection rather than a root specification in the second location block.
You could use alias or rewrite directive instead of redirect. And there is no need for regexp. And root directive inside location is bad practice.
server {
listen 8000;
server_name localhost;
access_log logs/host.access.log main;
root D:/Git/SNHAutomationRuby/output/screenshots;
location / {
}
location /img/ {
rewrite ^/img(.+) $1 last;
}
# or
location /img/ {
alias D:/Git/SNHAutomationRuby/output/screenshots;
}
}