Server Fault is a question and answer site for system and network administrators. It's 100% free, no registration required.

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

I have a location block setup to catch all file requests and send them to PHP-FPM:

location  / {
    try_files  $uri /routing.php?$args;
    fastcgi_pass   unix:/opt/local/var/run/php54/php-fpm-www.sock;
    include       /documents/projects/intahwebz/intahwebz/conf/fastcgi.conf;

This works and correctly passes the request to PHP-FPM to either the exact existing php file that was requested or with routing.php set as the script to run.

I tried to add an error page, so that if routing file was ever removed or otherwise not available, the error page would be shown rather than Nginx's default error page:

location  / {
    try_files $uri /routing.php?$args /50x_static.html;
    fastcgi_pass   unix:/opt/local/var/run/php54/php-fpm-www.sock;
    include       /documents/projects/intahwebz/intahwebz/conf/fastcgi.conf;

This stops the routing.php file from being served, and the 50x_static.html page is shown instead. Requests to an existing PHP file still work i.e. going to the URL /dynamic.php

I realise that the last parameter in a try_files command is slightly magical:

In the event that no file is found, an internal redirect to the last parameter is invoked. Do note that only the last parameter causes an internal redirect, former ones just sets the internal URI pointer. The last parameter is the fallback URI and must exist, or else an internal error will be raised.

While investigating why the error_page had broken the config, I realised that for the config that works (without the static error page), Nginx does appear to be matching the request twice according to the Nginx rewrite log when trying to get the root URL "/":

"^/proxy/(\d+)/(\w+)/(.+)\.(gif|png|jpg|jpeg|GIF|PNG|JPG|JPEG)$" does not match "/", client:, server:, request: "GET / HTTP/1.1", host: "basereality.test"
"^/proxy/(\d+)/(.+)\.(gif|png|jpg|jpeg|GIF|PNG|JPG|JPEG)$" does not match "/", client:, server:, request: "GET / HTTP/1.1", host: "basereality.test"
"^/staticImage/(\w+)/(.+)\.([^\.]*)$" does not match "/", client:, server:, request: "GET / HTTP/1.1", host: "basereality.test"
"^/proxy/(\d+)/(\w+)/(.+)\.(gif|png|jpg|jpeg|GIF|PNG|JPG|JPEG)$" does not match "/routing.php", client:, server:, request: "GET / HTTP/1.1", host: "basereality.test"
"^/proxy/(\d+)/(.+)\.(gif|png|jpg|jpeg|GIF|PNG|JPG|JPEG)$" does not match "/routing.php", client:, server:, request: "GET / HTTP/1.1", host: "basereality.test"
"^/staticImage/(\w+)/(.+)\.([^\.]*)$" does not match "/routing.php", client:, server:, request: "GET / HTTP/1.1", host: "basereality.test"

i.e. the request comes in as /, the try_files fails to serve the file and so rewrites the request from / to /routing.php and then reprocesses the request.

Why is try files not serving the routing.php file on the first pass? It exists and is accessible, otherwise it wouldn't be served on the second time round.


Removed unrelated config.

share|improve this question
Can you post the rest of the server block? – Michael Hampton May 17 '13 at 16:18
If your question has been sufficiently answered, remember to award your bounty. – Michael Hampton May 19 '13 at 16:53
up vote 2 down vote accepted

The documentation you quoted explicitly says “an internal redirect to the last parameter is invoked”. The internal redirect is handled in the same way as the initial request coming from the client — this includes processing the rewrite statements at the server level, which you see in the log. However, if any other try_files parameter except the last one matches an existing file, the request is handled using the location configuration where the try_files statement is located, and there will be no second matching.

As for your rules, did you try just omitting $args in try_files?

location  / {
    try_files $uri /routing.php /50x_static.html;
    fastcgi_pass   unix:/opt/local/var/run/php54/php-fpm-www.sock;
    include       /documents/projects/intahwebz/intahwebz/conf/fastcgi.conf;

Note that $uri does not contain $args too; query parameters will still be passed to the FastCGI backend through the QUERY_STRING parameter, which is presumably set in your fastcgi.conf:

fastcgi_param QUERY_STRING    $query_string;

And if neither $uri nor /routing.php are present as files, the request will be redirected to /50x_static.html and handled according to the location = /50x_static.html section in your config (but a second iteration of rewrite attempts will still be performed, because your rewrite rules are placed at the server level).

One very suspicious detail of your configuration is that you are passing all files through PHP irrespective of the file extension — this is very unusual, and may cause security issues due to PHP code execution in a file where it was not expected.

share|improve this answer
It was the "?args" but to note; All of the static images/css are etc picked up by the "(html|jpg|jpeg|gif|png..." location block. Also I have no 'enemy' user files uploaded to within the document root and have "cgi.fix_pathinfo=0". Unless I accidentally upload a bad script myself, I don't believe anyone else could execute a php file masquarading as a jpg file. – Danack May 17 '13 at 20:23

Sergey had it correct; adding $args to a try_files command stops the file from being matched and so sends Nginx round in another processing loop, which is odd, as that is what the Nginx example says to do.

If you follow the example exactly with:

try_files $uri $uri/ /index.php?q=$uri&$args;

If $uri and $uri/ don't exist, Nginx will move onto the magic last parameter. It doesn't even try to match the file and instead reprocesses the request setting the new path to /index.php and the $args to now be q=$uri&$args.

On the other hand, if you have another value in try_files after index.php e.g.

try_files $uri $uri/ /index.php?q=$uri&$args /50x_static.html;

Nginx looks for a file called /index.php?q=$uri&$args and it obviously doesn't exist, and so it moves onto the /50x_static.html and reprocesses that.

Because $uri is rewritten when Nginx reprocesses the request, avoiding this needs some subtle changes. If you set up your config to be like this:

set $originalURI  $uri;
try_files $uri $uri/ /index.php  /50x_static.html;
fastcgi_param  QUERY_STRING  q=$originalURI&$query_string;

The file Nginx looks for is /index.php which is the correct name, so nginx will stop processing there. We have to copy the $uri variable as the try_files modifies it and so it would be just /index.php if we just used it after the try_files.

share|improve this answer

Your Answer


By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.