Nginx rewite rules 403 error

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).