Nginx location block – only match actual files

nginxPHPstatic-content

I have the following nginx config:

server {
    listen 8080;

    root /site_root/web;
    index index.html;

    server_name www.mysite.com;

    location / {
        try_files $uri @rewriteapp;
    }

    location @rewriteapp {
        rewrite ^(.*)$ /app.php/$1 last;
    }

    # add headers to static files
    location ~* \.(?:ico|css|js|gif|jpe?g|png|svg|woff|ttf|eot)$ {
        expires 365d;
        add_header Pragma public;
        add_header Cache-Control "public";
    }

    location ~ \.php {
        fastcgi_split_path_info ^(.+\.php)(/.*)$;
        include fastcgi_params;
        fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info;
        fastcgi_param PATH_INFO $fastcgi_path_info;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param HTTPS $https;
        fastcgi_pass unix:/var/run/php-fpm-www.sock;
    }
}

Issue
This is a standard php application with front controller and some static assets (js/css etc) sitting as files on the filesystem (for arguments sake the location of these files is '/site_root/web/assets').

The intention of the above config is to add 'max-age' headers to these static files to allow browsers to cache them. This works for all files which exist on the filesystem. However I have some assets which are dynamically generated and need to pass through php ('/site_root/web/assets/dynamic/file.uk.js', '/site_root/web/assets/dynamic/file.us.js', '/site_root/web/assets/variable/variable.uk.js').

The issue is that the inclusion of the location directive 'add headers to static files' is causing those dynamic files to 404. How can I either (in order of how desirable each solution is):

  • Change the location directive to exclude files which don't exist on the server (using try_files/internal?)

  • Change the location directive to exclude paths which match (a whitelist e.g 'dynamic|variable')

Best Answer

Nginx only uses one location block

This comes down to the fact that nginx will only use one location block. Anything in other location blocks is ignored. In the question any request which matches the "static files" location block, whether the file exists or not, will be processed by that location block only. Whenever there's some ambiguity, I find a useful technique for debugging:

location /something-else {
    add_header "section" "something else location"; 
    # ^ if this location block is used, that header is in the output
    ...
}

In the headers for a response, the header added from the block which matched will be included:

$ curl -I "http://nginx.h5bp.dev/something"
...
section: something location # <- like so

Solutions

There are many solutions, and you might find this reference material useful to read. One thing to bear in mind is that nginx is intended to be used with prefix routing - that makes life easy for nginx, and you.

Prefix routing

So, if you can do this:

location ~ ^/(css|images|js)/ {
    expires 365d;
    add_header Pragma public;
    add_header Cache-Control "public";
}

location ~ \.php {
    ...
}

That would be the optimal solution. An extension to that using nested location blocks can also be used, if it's necessary:

location ~ ^/(css|images|js)/ {
    location ~* \.(?:whatever|ext)$ {
        expires 365d;
        add_header Pragma public;
        add_header Cache-Control "public";
    }
}

location ~ \.php {
    ...
}

Put try files in all location blocks

In the question, there is one location block - so this is an obvious alternative, as mentioned by Micheal:

location ~* \.(?:ico|css|js|gif|jpe?g|png|svg|woff|ttf|eot)$ {
    try_files $uri @rewriteapp; # <- added
    expires 365d;
    add_header Pragma public;
    add_header Cache-Control "public";
}

location ~ \.php {
    ...
}

If you have multiple location blocks though, this gets pretty tedious (though, also probably indicates not using nginx as designed)

404 Front controller

The will-always-work setup, irrespective of what your location blocks look like is to use a 404 front controller. In your case this would mean:

server {
    listen 8080;

    root /site_root/web;
    index index.html;

    server_name www.mysite.com;

    try_files $uri $uri/ @rewriteapp;
    error_page 404 = @rewriteapp

    location @rewriteapp {
        rewrite ^ /app.php/$request_uri last;
    }

    # add headers to static files
    location ~* \.(?:ico|css|js|gif|jpe?g|png|svg|woff|ttf|eot)$ {
        expires 365d;
        add_header Pragma public;
        add_header Cache-Control "public";
    }

    location ~ \.php {
        fastcgi_split_path_info ^(.+\.php)(/.*)$;
        include fastcgi_params;
        fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info;
        fastcgi_param PATH_INFO $fastcgi_path_info;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param HTTPS $https;
        fastcgi_pass unix:/var/run/php-fpm-www.sock;
    }
}

Or similar. Pick the solution that works for you and is the simplest.