3

I am having trouble with converting .htaccess files to nginx. I have 3 .htaccess files. The first .htaccess file is located in the document root and is as follows:

Options +FollowSymLinks
RewriteEngine On
RewriteRule ^img-(.*)\.html img.php?id=$1 [L]
RewriteRule ^slide-(.*)\.html slider.php?id=$1 [L]
RewriteRule ^page-(.*)\.html page.php?name=$1 [L]
RewriteRule ^contact\.html$ contact.php [QSA,L,NC]

The second .htaccess file is located in a folder called upload:

RewriteEngine On
RewriteCond %{HTTP_REFERER} !^http://(.+\.)?foo\.com/ [NC]
RewriteCond %{HTTP_REFERER} !^$
RewriteRule .*\.(jpe?g|gif|bmp|png)$ nohotlink.gif [L]
<Files ~ "\.(php|sql|php3|php4|phtml|pl|py|jsp|asp|htm|shtml|sh|cgi)$">
  order allow,deny
  deny from all
</Files>

And the third and final .htaccess file is found in a subdirectory of the upload folder named "small":

RewriteEngine Off

Now I have created a folder in /etc/nginx called includes and made 3 seperate .access files with these rewrite rules:

For the first .htaccess file located in the document root directory I have created an file in /etc/nginx/includes called root.access. In this file I have:

# nginx configuration 

location /img { 
rewrite ^/img-(.*)\.html /img.php?id=$1 break; 
} 
location /slide { 
    rewrite ^/slide-(.*)\.html /slider.php?id=$1 break; 
} 
location /page { 
    rewrite ^/page-(.*)\.html /page.php?name=$1 break; 
} 
location /contact { 
    rewrite ^/contact\.html$ /contact.php break; 
}

For the second file located in the "upload" folder I have crated a file in /etc/nginx/includes called upload.access. This file contains the following:

# nginx configuration 

location /upload { 
if ($http_referer !~ "^http://(.+\.)?foo\.com/"){ 
rewrite .*\.(jpe?g|gif|bmp|png)$ /nohotlink.gif break; 
} 
} 
location ~ \.(php|sql|php3|php4|phtml|pl|py|jsp|asp|htm|shtml|sh|cgi)$ { 
deny all; 
}

And for the third and final file I have created a file in /etc/nginx/includes names small.access. The contents of this file are as follows:

# nginx configuration 

location /upload/small { 
}

In the server block configuration file I have:

 location / {
              try_files $uri $uri/ /index.php;
              include /etc/nginx/includes/root.access;
     }

 location /upload {
              include /etc/nginx/includes/upload.access;
     }

 location /upload/small {
              include /etc/nginx/includes/small.access;
     }

Now with this configuration when trying the site I receive 403 errors. Nginx error log reports:

[error] 18156#0: *7 access forbidden by rule, client: 111.**.**.**, server: foo.com, request: "POST /upload.php HTTP/1.1", host: "foo.com", referrer: "http://foo.com/"

Now under apache everything works no problems. But I can not see why I am getting 403 errors. I am also worried that the rewrite rules as I have stated them, beyond the 403 error will not function properly at all. Can anyone help me with this? I can not see what is wrong here.

Adrian
  • 67
  • 3
  • 8

1 Answers1

10

This is the standard nginx behaviour for this part of your configuration :

location /upload { 

    if ($http_referer !~ "^http://(.+\.)?foo\.com/"){ 
        rewrite .*\.(jpe?g|gif|bmp|png)$ /nohotlink.gif break; 
    } 

    location ~ \.(php|sql|php3|php4|phtml|pl|py|jsp|asp|htm|shtml|sh|cgi)$ { 
        deny all; 
    }

}

Why?

Let me clarify how locations work : when nginx reads configuration files, it will sort location blocks under 3 types :

  • Exact location blocks e.g. location = /upload { }
  • Prefixed location blocks e.g. location /upload { }
  • Location blocks containing regular expressions e.g. location ~ /upload { }

Once a request comes to nginx, the location selection process is as follows :

  1. If an exact location block matching the URI is found, nginx stops searching for other location blocks and serve this request.
  2. If not, nginx will search for the longest matching prefixed location block and will memorise it before going to the next step.
  3. Then nginx checks for location blocks containing regular expressions sequentially. The first matching one will be used to serve the request.
  4. If nothing was found at the previous step, then nginx will use the prefixed location block of step 2 to serve the request. If none was found several things can happen, but it's off-topic.

So in your case, the location block matching the .php extension takes precedence over the location block matching the /upload part of the URI.

Edit : Clarification on solution, alternative solution added.

Using the ^~ you can tell nginx to change his behaviour for step 2 so it will use the matching prefixed location immediately, bypassing regular expression locations lookup. So you have 2 solutions :

Solution 1 :

upload.access :

if ($http_referer !~ '^http://(.+\.)?foo\.com/') { 
    rewrite '.*\.(jpe?g|gif|bmp|png)$' '/upload/nohotlink.gif' break; 
} 

if ($uri ~ '\.(php|sql|php3|php4|phtml|pl|py|jsp|asp|htm|shtml|sh|cgi)$') {
    return 403; 
}

same server block

Solution 2 :

upload.access :

if ($http_referer !~ '^http://(.+\.)?foo\.com/') { 
    rewrite '.*\.(jpe?g|gif|bmp|png)$' '/upload/nohotlink.gif' break; 
} 

location ~ \.(php|sql|php3|php4|phtml|pl|py|jsp|asp|htm|shtml|sh|cgi)$ {
    deny all;
}

server block :

 location ^~ /upload {
     include /etc/nginx/includes/upload.access;
 }

Now, your current configuration will not lead to anything if you don't set up a location to forward php file processing : check out nginx fastcgi module. Then you will need to change your rewrite rules in the root.access file so they are not resolved in the current location context (i.e. create one unique fallback location and change break to last to tell nginx to run the location selection process again after rewrite).

Xavier Lucas
  • 12,815
  • 2
  • 44
  • 50
  • This is exactly what I needed. Thank you. I appreciate the detailed and concise explanation. However, this bit produces another error. location ~ \.(php|sql|php3|php4|phtml|pl|py|jsp|asp|htm|shtml|sh|cgi)$ { deny all; I am not sure what the effect will be if I change it to location ^~ \.(php|******) or if I should even do it at all. What would be the proper way to write this into the file? } – Adrian Sep 19 '14 at 00:50
  • @Adrian Check the updated answer. – Xavier Lucas Sep 19 '14 at 08:11
  • Sorry for the delay Xavi. Thank you so much for your help it worked perfectly. Absolutely Brilliant. :) Thanks again. – Adrian Sep 19 '14 at 21:50