Nginx – Multiple static files directories, single PHP FPM server

nginxPHPphp-fpm

I have two directories which I need to serve static assets out of:

  1. /srv/web: Static assets which include images, JavaScript, HTML, etc.
  2. /srv/php: Dynamic PHP scripts alongside some static assets.

I'm using NGINX and have configured it like so:

server {
    listen 80;
    server_name _;

    # root /;
    index index.php index.html index.htm;
    try_files /srv/web/$uri /srv/php/$uri =404;

    location ~ \.php$ {
        root /srv/php;
        try_files $uri =404;
        fastcgi_pass unix:/var/run/php5-fpm.sock;
        fastcgi_index index.php;
#       fastcgi_param SCRIPT_FILENAME /srv/php$fastcgi_script_name;
        include fastcgi_params;
    }
}

I'm on Ubuntu 14.04, PHP FPM package version is 5.5.9, NGINX package version is 1.4.6.

The simple goal here is to serve static files out of /srv/web first, failing that /srv/php, failing that, return 404. All files ending in \.php$ will be requested from PHP FPM over the Unix socket, and this is working.

The problem I'm currently having is that the index directive on the server isn't working as planned. I have an index.html file in /srv/web, and when I do

curl -is http://localhost/

I get a 404.

Is this the most ideal way of setting up an NGINX site with multiple filesystem folders to serve from alongside PHP? Any ideas as to why my static index isn't working?


Update

As per AD7six's answer below, I've updated my configuration to look like this:

server {
    listen 80;
    server_name _; # listen at all host names

    # serve static files first from /srv/web, then from /srv/php, and any dynamic PHP files from
    # FastCGI/FPM at the Unix socket.
    location / {
        root /srv/web;
        index index.html index.htm;
        try_files $uri $uri/ @php;
    }

    location @php {
        root /srv/php;
        index index.php;
        try_files $uri $uri/ =404;
    }

    location ~ \.php$ {
        root /srv/php;
        fastcgi_pass unix:/var/run/php5-fpm.sock;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME /srv/php/$fastcgi_script_name;
        include fastcgi_params;
    }
}

My directory listing looks like this:

/srv/
|-- php
|   |-- demo.php
|   |-- index.php
|   `-- phpstatic.txt
`-- web
    |-- static.html
    `-- subdir
        `-- index.html

3 directories, 5 files

Getting static files and PHP files works as planned and getting /subdir/ with its index works fine, but if I GET /, I get a 403 forbidden, and nginx complains of directory listing:

2015/08/24 21:50:59 [error] 9159#0: *7 directory index of "/srv/web/" is forbidden, client: 127.0.0.1, server: _, request: "GET / HTTP/1.1", host: "localhost"

Not sure why this is failing, but it smells like progress at least.

Best Answer

Multiple roots won't work like that

With this config:

server {
    # root /;
    index index.php index.html index.htm;
    try_files /srv/web/$uri /srv/php/$uri =404;

There is no request processing that would use the index directive, as written the request must match a file. Using the debug log confirms this:

2015/08/24 08:12:11 [debug] 17173#0: *26 try files phase: 13
2015/08/24 08:12:11 [debug] 17173#0: *26 http script copy: "/srv/web/"
2015/08/24 08:12:11 [debug] 17173#0: *26 http script var: "/"
2015/08/24 08:12:11 [debug] 17173#0: *26 trying to use file: "/srv/web//" "/srv/web//"
2015/08/24 08:12:11 [debug] 17173#0: *26 http script copy: "/srv/php/"
2015/08/24 08:12:11 [debug] 17173#0: *26 http script var: "/"
2015/08/24 08:12:11 [debug] 17173#0: *26 trying to use file: "/srv/php//" "/srv/php//"
2015/08/24 08:12:11 [debug] 17173#0: *26 trying to use file: "=404" "=404"

Using a try_files directive that does look for directories like this:

try_files /srv/web/$uri /srv/web/uri/ /srv/php/$uri /srv/php/$uri/ =404;

also doesn't work:

2015/08/24 08:16:17 [debug] 17651#0: *33 http script copy: "/srv/web/"
2015/08/24 08:16:17 [debug] 17651#0: *33 http script var: "/srv/web//index.html"
2015/08/24 08:16:17 [debug] 17651#0: *33 trying to use file: "/srv/web//srv/web//index.html" "/srv/web//srv/web//index.html"
2015/08/24 08:16:17 [debug] 17651#0: *33 http script copy: "/srv/web/"
2015/08/24 08:16:17 [debug] 17651#0: *33 http script var: "/srv/web//index.html"
2015/08/24 08:16:17 [debug] 17651#0: *33 trying to use dir: "/srv/web//srv/web//index.html" "/srv/web//srv/web//index.html"
2015/08/24 08:16:17 [debug] 17651#0: *33 http script copy: "/srv/php/"
2015/08/24 08:16:17 [debug] 17651#0: *33 http script var: "/srv/web//index.html"
2015/08/24 08:16:17 [debug] 17651#0: *33 trying to use file: "/srv/php//srv/web//index.html" "/srv/php//srv/web//index.html"
2015/08/24 08:16:17 [debug] 17651#0: *33 http script copy: "/srv/php/"
2015/08/24 08:16:17 [debug] 17651#0: *33 http script var: "/srv/web//index.html"
2015/08/24 08:16:17 [debug] 17651#0: *33 trying to use dir: "/srv/php//srv/web//index.html" "/srv/php//srv/web//index.html"
2015/08/24 08:16:17 [debug] 17651#0: *33 trying to use file: "=404" "=404"

Note that the "root" is confused, try_files expects a url not a file path. I suggest not to continue trying to use a solution of this kind - especially not setting the root as / and potentially allowing access to any file on the server.

Use two location blocks

Instead keep things simple. This config will serve all the static content:

    root /srv/web;
    index index.html;
    try_files $uri $uri/;

This config serves all php content:

    root /srv/php;
    index index.php;
    try_files $uri $uri/;

Just put them together:

location / {
    root /srv/web;
    index index.html;
    try_files $uri $uri/ @php;
    error_page 403 = @php; # see note below
}

location @php {
    root /srv/php;
    index index.php;
    try_files $uri $uri/ =404;
}

location ~ \.php$ {
    # as before
}

One gotcha is that with this kind of setup a request which matches a folder under /srv/web which doesn't have an index.html file will throw a 403 error (as the request is for a directory, and there is no directory index file). To allow these requests to also be handled by php - it's necessary to redirect 403 errors to the php handler.