Nginx – Cannot get simple nginx rewrite rule working in subdirectory

nginxrewrite

We are just migrating to nginx from Apache 2 and are having a few issues with some rewrite rules. The following is what used to work on Apache:

RewriteEngine on
Options +FollowSymLinks

#RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} !-f
#RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} !-d

RewriteRule ^(.*)/p([0-9]+)-(.*)-page([0-9+]).html$ showproduct.php?product=$2&cpage=$4 [L]
RewriteRule ^(.*)/p([0-9]+)-(.*).html$ showproduct.php?product=$2 [L]
RewriteRule ^(.*)/p([0-9]+).html$ showproduct.php?product=$2 [L]

RewriteRule ^g([0-9]+)-(.*)-page([0-9]+).html$ showcat.php?cat=$1&page=$3 [L]
RewriteRule ^g([0-9]+)-(.*).html$ showcat.php?cat=$1 [L]

RewriteRule ^(.*)/index([0-9]+)-([0-9]+).html$ index.php?cat=$2&page=$3 [L]
RewriteRule ^(.*)/index([0-9]+).html$ index.php?cat=$2 [L]

RewriteRule ^m([0-9]+)-(.*)-protype([0-9]+).html$ member.php?uid=$1&protype=$3 [L]
RewriteRule ^m([0-9]+)-(.*).html$ member.php?uid=$1 [L]

RewriteRule ^board.html$ board.php [L]
RewriteRule ^b([0-9]+)-(.*).html$ board.php?msg=$1 [L]

RewriteRule ^u([0-9]+)-(.*)-page([0-9]+).html$ showcat.php?ppuser=$1&page=$3 [L]
RewriteRule ^u([0-9]+)-(.*).html$ showcat.php?ppuser=$1 [L]

RewriteRule ^s([0-9]+)-(.*)-page([0-9]+).html$ showmembers.php?cat=$1&page=$3 [L]
RewriteRule ^s([0-9]+)-(.*).html$ showmembers.php?cat=$1 [L]

And this is what we turned it into:

location /classifieds/ {

rewrite ^/classifieds/(.*)/p([0-9]+)-(.*)-page([0-9+]).html$ /classifieds/showproduct.php?product=$2&cpage=$4 permanent;
rewrite ^/classifieds/(.*)/p([0-9]+)-(.*).html$ /classifieds/showproduct.php?product=$2 permanent;
rewrite ^/classifieds/(.*)/p([0-9]+).html$ /classifieds/showproduct.php?product=$2 permanent;

rewrite ^/classifieds/g([0-9]+)-(.*)-page([0-9]+).html$ /classifieds/showcat.php?cat=$1&page=$3 permanent;
rewrite ^/classifieds/g([0-9]+)-(.*).html$ /classifieds/showcat.php?cat=$1 permanent;

rewrite ^/classifieds/(.*)/index([0-9]+)-([0-9]+).html$ /classifieds/index.php?cat=$2&page=$3 permanent;
rewrite ^/classifieds/(.*)/index([0-9]+).html$ /classifieds/index.php?cat=$2 permanent;

rewrite ^/classifieds/m([0-9]+)-(.*)-protype([0-9]+).html$ /classifieds/member.php?uid=$1&protype=$3 permanent;
rewrite ^/classifieds/m([0-9]+)-(.*).html$ /classifieds/member.php?uid=$1 permanent;

rewrite ^/classifieds/board.html$ /classifieds/board.php permanent;
rewrite ^/classifieds/b([0-9]+)-(.*).html$ /classifieds/board.php?msg=$1 permanent;

rewrite ^/classifieds/u([0-9]+)-(.*)-page([0-9]+).html$ /classifieds/showcat.php?ppuser=$1&page=$3 permanent;
rewrite ^/classifieds/u([0-9]+)-(.*).html$ /classifieds/showcat.php?ppuser=$1 permanent;

rewrite ^/classifieds/s([0-9]+)-(.*)-page([0-9]+).html$ /classifieds/showmembers.php?cat=$1&page=$3 permanent;
rewrite ^/classifieds/s([0-9]+)-(.*).html$ /classifieds/showmembers.php?cat=$1 permanent;
}

Which does not work. All we get when we try to visit our URLs are nginx 404s. We are still very new to nginx (and these rules are for third-party software that we cannot modify) so any pointers would be appreciated.

**Update: ** Following is some more (hopefully) useful information. When requesting a URL, the logs indicate that the 404 is happening before the rewrite takes place:

2013/03/11 10:38:18 [error] 10073#0: *13003643 open() "/home/site/public_html/classifieds/category-name-here/p1097-product.html" failed (2: No such file or directory), client: IP, server: site.com, request: "GET /classifieds/category-name-here/p1097-product.html HTTP/1.1", host: "www.site.com"

The full configuration file is below:

server {
    server_name site.com www.site.com;
    root "/home/site/public_html";

    index index.php;
    client_max_body_size 10m;

    access_log /home/site/_logs/access.log;
    error_log /home/site/_logs/error.log;

    location /classifieds/ {

        rewrite ^/classifieds/(.*)/p([0-9]+)-(.*)-page([0-9+]).html$ /classifieds/showproduct.php?product=$2&cpage=$4 permanent;
        rewrite ^/classifieds/(.*)/p([0-9]+)-(.*).html$ /classifieds/showproduct.php?product=$2 permanent;
        rewrite ^/classifieds/(.*)/p([0-9]+).html$ /classifieds/showproduct.php?product=$2 permanent;

        rewrite ^/classifieds/g([0-9]+)-(.*)-page([0-9]+).html$ /classifieds/showcat.php?cat=$1&page=$3 permanent;
        rewrite ^/classifieds/g([0-9]+)-(.*).html$ /classifieds/showcat.php?cat=$1 permanent;

        rewrite ^/classifieds/(.*)/index([0-9]+)-([0-9]+).html$ /classifieds/index.php?cat=$2&page=$3 permanent;
        rewrite ^/classifieds/(.*)/index([0-9]+).html$ /classifieds/index.php?cat=$2 permanent;

        rewrite ^/classifieds/m([0-9]+)-(.*)-protype([0-9]+).html$ /classifieds/member.php?uid=$1&protype=$3 permanent;
        rewrite ^/classifieds/m([0-9]+)-(.*).html$ /classifieds/member.php?uid=$1 permanent;

        rewrite ^/classifieds/board.html$ /classifieds/board.php permanent;
        rewrite ^/classifieds/b([0-9]+)-(.*).html$ /classifieds/board.php?msg=$1 permanent;

        rewrite ^/classifieds/u([0-9]+)-(.*)-page([0-9]+).html$ /classifieds/showcat.php?ppuser=$1&page=$3 permanent;
        rewrite ^/classifieds/u([0-9]+)-(.*).html$ /classifieds/showcat.php?ppuser=$1 permanent;

        rewrite ^/classifieds/s([0-9]+)-(.*)-page([0-9]+).html$ /classifieds/showmembers.php?cat=$1&page=$3 permanent;
        rewrite ^/classifieds/s([0-9]+)-(.*).html$ /classifieds/showmembers.php?cat=$1 permanent;
    }   

    location / {
                try_files $uri $uri/ /index.php$uri?$args;
    }


        location ~ "^(.+\.php)($|/)" {
        fastcgi_split_path_info ^(.+\.php)(.*)$;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param SCRIPT_NAME $fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
                fastcgi_param SERVER_NAME $host;

        if ($uri !~ "^/uploads/") {
            fastcgi_pass   127.0.0.1:9006;
        }
        include        fastcgi_params;
    }


        location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {
                        expires max;
                        log_not_found off;
                        access_log off;
        }

        location ~* \.(html|htm)$ {
                expires 30m;
        }

        location ~* /\.(ht|git|svn) {
                deny  all;
        }
}

**Update 2: ** After changing my location line to location ^~ /classifieds/, we finally were able to get the rewrites working, but were experiencing a redirect loop. As an example, the URL /classifieds/g2-general-category.html was properly being rewritten to /classifieds/showcat.php?cat=2 but it appears that the script was detecting this rewrite and attempting to send the user to the proper SEO URL, which rewrote to showcat.php, ad infinitum. Additionally, after the rewrite loop was finished, my browser URL bar would indicate showcat.php 50% of the time and g2-general-category.html the other 50% of the time. So I guess my question is – How can we avoid this behavior? To my untrained eye, it looks like what is happening is that:

User visits g2-general-category.html.
nginx sends user to showcat.php?cat=2
PHP script sees that user is not using the SEO'd URL, so does a header redirect to send them to the correct URL, where nginx then rewrites it to showcat.php?cat=2 again.

Best Answer

The rewrite directives seem to be correct.

However, from your provided information I can't tell you much about what is wrong.

  • Find out where the 404 happens: before or after the redirect.
    Examine the log files or use in-browser development tools (like Firebug) to follow the redirect flow.

  • Is that given snippet all you got inside the server block? What about the logic that handles the redirect targets?
    You should post the rest as well.

  • Also, look out for other location blocks possibly interfering with the URLs.
    See the nginx docs for the order of matching locations.

Update: Guess I found your mistake:

The location ~* \.(html|htm)$ block matches all your requests usually going to /classifieds/ because regular expression are prefered by the mathing engine by default.
To raise priority of location /classifieds/ you should use the ^~ flag:

location ^~ /classifieds/ {
    # REWRITES HERE
}

Corresponding docs entry:

It is possible to disable regular expression checks after literal string matching by using "^~" prefix. If the most specific match literal location has this prefix: regular expressions aren't checked.

Redirect loop:

It seems like you want to keep the SEO-friendly URIs for outer appearance.
In this case, modify the rewrites to only rewrite internally - without redirecting the browser.

To achieve this behaviour, use the last flag instead of permanent as follows:

rewrite SEO-PATTERN INTERNAL-URI last;

The location match engine will then try to match the rewritten URI.
While redirect and permanent trigger a HTTP redirect (temporary/permanent), last and break will only affect the internal URI value.
It is all described in the docs.