nginx cache images generated by backend

My application generates dynamic content and I try to cache them using browser cache. So If I try to add to my server block the following:

location ~*  \.(jpg|jpeg|png|gif|ico|css|js)$ {
    expires 365d;
}

Nginx no longer pass the request to the backend to generate the assets and it ends up with http status code 404

Instead, adding those 2 following conditions could solve my problem (taken from this question):

location / {
    if ($upstream_http_content_type ~ "(image/jpeg)|(image/png)|(image/gif)|(image/svg+xml)|(text/css)|(text/javascript)|(application/javascript)") {
      expires 90d;
    }

    if ($sent_http_content_type ~ "(image/jpeg)|(image/png)|(image/gif)|(image/svg+xml)|(text/css)|(text/javascript)|(application/javascript)") {
      expires 90d;
    }
    try_files $uri @rewriteapp;
}

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

location ~ ^/(site|site_dev|admin|admin_dev)\.php(/|$) {
    # it's PHP that generates some of my assets
    fastcgi_pass unix:/var/run/php/php7.1-fpm.sock;
    fastcgi_split_path_info ^(.+\.php)(/.*)$;
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_param HTTPS on;
}

The problem is that I can't see in the http response headers any date that indicates that it's working and images will be served from browser cache for 90 days.

What am I missing ?


Solution 1:

As you generate the HTTP response in your PHP application you should look also at your application regarding the response headers. Browser caching is only a HTTP response header issue. So if your application provides proper headers your web server will serve them and your browser will cache the result.

However you can overwrite this behavior in the web server. But it's never a good idea to do this on web server level. Here you have only URL and request headers to decide if a response should be cached or not. Your application has usually more details to make better decisions.

For the best approach fix your PHP code by sending proper caching headers:

<?php
...
header('Cache-Control: max-age='.90*24*3600);

For the alternate approach you can (but you should not) use this in the Nginx config to overwrite the web app's response:

location ~ ^/(site|site_dev|admin|admin_dev)\.php(/|$) {
    # it's PHP that generates some of my assets
    fastcgi_pass unix:/var/run/php/php7.1-fpm.sock;
    fastcgi_split_path_info ^(.+\.php)(/.*)$;
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_param HTTPS on;

    # hide response header sent by PHP
    fastcgi_hide_header Cache-Control;
    fastcgi_hide_header Expires;
    # set Cache-Control response for client
    expires 90d;
}

A better way would be caching on web server level. Here you can configure exceptions and be able to react to special cases like a "Set-Cookie" header in the HTTP response.

By the way new visitors will get an advantage of fast delivery as well. Browser based caching does not help first time visitors.

location ~ ^/(site|site_dev|admin|admin_dev)\.php(/|$) {
    # it's PHP that generates some of my assets
    fastcgi_pass unix:/var/run/php/php7.1-fpm.sock;
    fastcgi_split_path_info ^(.+\.php)(/.*)$;
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_param HTTPS on;

    # activate caching (you must define a cache storage for phpfpm)
    fastcgi_cache          phpfpm;
    # enable caching for 90 days for responses with HTTP status 200
    fastcgi_cache_valid    200 90d;
    # allow headers from PHP to client
    fastcgi_pass_header    Set-Cookie;
    fastcgi_pass_header    Cookie;
    # ignore headers from PHP to client
    fastcgi_ignore_headers Cache-Control Expires Set-Cookie;
    # remove headers from PHP to client
    fastcgi_hide_header    Cache-Control;
    fastcgi_hide_header    Expires;
    # don't save in cache
    fastcgi_no_cache       $cookie_PHPSESSID;
    # don't serve from cache
    fastcgi_cache_bypass   $cookie_PHPSESSID;
}

Please take care that this is only a recommendation based on standard settings. It may vary in your setup.