Nginx rewrite non-existent files under sub directory with rewrite rules

Hi everyone,

I am trying to move a site with a large number (93) of .htaccess files
from
Apache to Nginx.
There are over 800 rewrite rules to convert. To optimize performance, I
have decided to
place the rules under separate nested location blocks (61 blocks in
total).

But I am having problem with rewriting some of the rules. I have created
a
sample scenario to explain the problem.

Requirements:

  1. /test/abc/pics/ should redirect to /test/abc/wallpapers/
  2. /test/abc/pics/800/a.jpg should be served directly, if the file is
    present. If it does not exist, then the request should be rewritten
    to
    /test/abc/pics/pic.php, which will create the file on first request.
    Subsequent requests will be served directly.
  3. /test/abc should redirect to /test/abc.html
  4. /test/abc.html should be rewritten to /test/index.php
  5. /test/abc/def/year-2014.php should be rewritten to
    /test/abc/def/index.php
  6. All static files (.gif, .jpg, .png, .mpg…) should be served
    directly with a very long expiry time.
  7. All files ending in .html should be served directly. If the file
    does
    not exist the request should be rewritten to /fallback.php
  8. All files ending in .php or / should be executed by PHP-FPM. If
    the
    file does not exist it should be rewritten to /fallback.php

Configuration:

index index.php; # # Location Specific Rules # location /test/abc/ {
location /test/abc/pics/ { # For ensuring ‘serve directly, if file
exists’
condition of Rule 2 try_files $uri @rewrite_pics; } location
/test/abc/def/
{ try_files $uri $uri/index.php @rewrite_def; } } location /test/ {
rewrite
“^/test/([a-z-]+)$” /test/$1.html permanent; # Rule 3 rewrite
“^/test/([a-z-]+).html$” /test/index.php?symbol=$1 last; # Rule 4 }
location @rewrite_pics { rewrite “^/test/abc/pics/$”
/test/abc/wallpapers/
permanent; # Rule 1 rewrite "^/test/abc/pics/([0-9]+)/(.)$"
/test/abc/pics/pic.php?width=$1&height=$1&pic=$2 last; # Rule 2 }
location
@rewrite_def { rewrite “(?i)^/test/abc/def/year-([a-z]+).php$”
/test/abc/def/index.php?year=$1 last; # Rule 5 } # # Fallback Rules #
location ~

.(gif|jpg|jpeg|png|ico|3gp|flv|mpg|mpeg|mp4|mp3|swf|txt|xml|pdf|zip)$ {
expires 1M; # Rule 6 try_files $uri /404.php; } location ~
(.php|.html|/)$ { try_files $uri “${uri}index.php” /fallback.php =404;

Rules 7 & 8 include php-fpm.conf; }

*php-fpm.conf *

include fastcgi.conf;
fastcgi_index index.php;
fastcgi_pass 127.0.0.1:9000;

Problem

  1. Since the fallback rules are regular expressions, they are
    selected
    instead of the location specific rules
  2. If I use ~ prefix for all location specific rules, then the
    fallback
    rules are never executed. Request for /test/abc/pics/800/a.jpg will
    be
    rewritten to /test/abc/pics/pic.php which will again match the same
    location config. So the file is not executed and is downloaded as is.
  3. So, I added ‘include php-fpm.conf;’ to all location blocks (except
    named locations). But this causes all requests under /test/abc/pics/
    (including existing .jpg files) to pass to the PHP-FPM. Which leads
    to an
    access denied error, since only .php extension is allowed by
    security.limit_extensions.

What am I doing wrong here? Is there a simple way to have all existing
files served depending on their extension and only non existent files
match
the location blocks / rewrite rules?

Thanks & Regards,
Joyce B.

I am getting a new dedicated server and need to decide between Nginx and
Apache Event MPM. I have received expert advice to use Nginx, but unless
I
have a solution to this problem I am unable to move to Nginx. I have
been
trying to solve this for the past 3 days. I have been checking the Nginx
debug log with several configuration, and found that the problem is
order
of matching. Unfortunately, I can’t find any sample Nginx configuration
which rewrites a .php file to another .php file within a subdirectory
location block.

Any help or pointer in the right direction is much appreciated.

Thank You

Hi Francis,

Thank you for the suggestions. I will try them and revert.

Regards,
Joyce B.

On Wed, Nov 26, 2014 at 05:35:06PM +0530, Joyce B. wrote:

Hi there,

I am trying to move a site with a large number (93) of .htaccess files from
Apache to Nginx.

There isn’t always a 1:1 correspondence between apache and nginx config,
so some things will differ.

The suggestions below do do part of what you ask, but probably do not
do all of what you want.

  1. /test/abc/pics/ should redirect to /test/abc/wallpapers/

location = /test/abc/pics/ { return 301 /test/abc/wallpapers/; }

  1. /test/abc/pics/800/a.jpg should be served directly, if the file is
    present. If it does not exist, then the request should be rewritten to
    /test/abc/pics/pic.php, which will create the file on first request.
    Subsequent requests will be served directly.

location /test/abc/pics/ { try_files $uri /test/abc/pics/pic.php; }
location = /test/abc/pics/pic.php {
fastcgi_pass …;
fastcgi_param SCRIPT_FILENAME $document_root$request_uri;
# and something about QUERY_STRING - details aren’t in these
requirements.
# Possibly pic.php can be made to handle things without
QUERY_STRING?
}

  1. /test/abc should redirect to /test/abc.html
  2. /test/abc.html should be rewritten to /test/index.php

I’m not sure which parts of “redirect” and “rewrite” are important.

Possibly these two can be

location /test/ { try_files $uri $uri.html @index; }
location @index {
fastcgi_pass …;
fastcgi_param SCRIPT_FILENAME $document_root/test/index.php;
}

  1. /test/abc/def/year-2014.php should be rewritten to
    /test/abc/def/index.php

This might be a “return” or a “rewrite” or just a “fastcgi_pass” with
a suitable SCRIPT_FILENAME.

  1. All static files (.gif, .jpg, .png, .mpg…) should be served
    directly with a very long expiry time.

Each location with “try_files” should include the very long expiry time.

(Probably one “add_header” would do it.)

Note that “files” and “requests” are different things. It may make a
difference here.

  1. All files ending in .html should be served directly. If the file does
    not exist the request should be rewritten to /fallback.php

How does this fit in with the first requirement? Or are there no “html”
files in the /test/abc/pics/ hierarchy?

location ~ .html$ { try_files $uri @fallback; }

but the full location-matching rules should be consulted depending on
what actual files are present.

location @fallback {
fastcgi_pass …;
fastcgi_param SCRIPT_FILENAME $document_root/fallback.php;
}

  1. All files ending in .php or / should be executed by PHP-FPM. If the
    file does not exist it should be rewritten to /fallback.php

location ~ .php$ {
try_files $uri @fallback;
fastcgi_pass …;
fastcgi_param SCRIPT_FILENAME $document_root$request_uri;
}

What “file” corresponds to a request that ends in “/”? You’ll want

index index.php;
fastcgi_index index.php;

somewhere. Perhaps

location ~ /$ {
try_files $uri/ @fallback;
fastcgi_pass …;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}

would work.

Configuration:

Something seems to have mangled the whitespace here, making it hard to
read.

But using the above an inspiration, hopefully you’ll be able to find
something that fits your needs.

The basic idea is “avoid rewrite as much as possible”.

  1. Since the fallback rules are regular expressions, they are selected
    instead of the location specific rules

Above, the fallback rules are internal or exact.

That should mostly avoid problems 2 and 3, since they no longer need to
happen (I think).

Cheers,

f

Francis D. [email protected]