1

I need to set up NGINX so that different locations would have different expire times for caching and some would have caching disabled. This is my current setup:

fastcgi_cache_path /etc/nginx/cache levels=1:2 keys_zone=CACHE:200m inactive=100m;
fastcgi_cache_key "$scheme$request_method$host$request_uri";
server {
    ...
    set $no_cache 1;
    ...
    if ($request_uri ~* "/some/cached/route") {
        set $no_cache 0;
    }
    ...
    location ~ \.php$ {
        fastcgi_ignore_headers Cache-Control Expires Set-Cookie;
        fastcgi_cache CACHE;
        fastcgi_cache_valid 200 30m;       
        fastcgi_cache_bypass $no_cache;
        fastcgi_no_cache $no_cache;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass unix:/var/run/php/php7.0-fpm.sock;
        fastcgi_index index.php;
        include fastcgi_params;
    }
    ...
}

With this setup I can control when the response should be cached but cannot control how long the cache will be valid.

How could I make the cache valid time variable?

2 Answers 2

2

Original reply

Use different caches

fastcgi_cache_path /dev/shm/c1 levels=1:2 keys_zone=C1:50m inactive=1440m;
fastcgi_cache_path /dev/shm/c2 levels=1:2 keys_zone=C2:50m inactive=500m;
fastcgi_cache_path /dev/shm/c3 levels=1:2 keys_zone=C2:50m inactive=10m;

# absolute path
location = /path/to/resource { 
  fastcgi_cache C1; # etc
}

# regular expression
location ~* wp-admin { 
  fastcgi_cache C2; # etc
}

# catch-all
location ~ \.php$ {
  fastcgi_cache C3; # etc
}

Note you'll have to understand the order Nginx processes requests to get this right, and you'll probably have to use regular expression matching - I find this regex website very useful for testing. Don't use "if". If is evil.

Update. This idea comes from my Nginx configuration files, which are working on my websites. You can download my configurations on my nginx tutorial, part one.

Update 1

As requested, here's a more complete config. This is cut down from the link on the page above as it's pretty long. This shows just the main PHP block, not the other stuff I do such as wp-admin, static image hotlink protection, and forwards from http to https, etc

# Caching. Putting the cache into /dev/shm keeps it in RAM, limited to 10MB, for one day.
# You can move to disk if you like, or extend the caching time
fastcgi_cache_path /dev/shm/hr_nginxcache levels=1:2 keys_zone=HR_CACHE:50m inactive=1440m; #RAM

upstream php {
   server 127.0.0.1:9001;
}


# http production headphone reviews server
server {
  server_name www.example.com;
  listen 443 ssl http2;

  ssl_certificate /var/lib/acme/certs/***CERT_DIRECTORY/fullchain;
  ssl_certificate_key /var/lib/acme/certs/***CERT_DIRECTORY/privkey;

  # Set up preferred protocols and ciphers. TLS1.2 is required for HTTP/2
  ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
  ssl_prefer_server_ciphers on;
  ssl_ciphers ECDH+AESGCM:ECDH+AES256:ECDH+AES128:DH+3DES:!ADH:!AECDH:!MD5;

  root /var/www/***folder;

  # First line is a cached access log, second logs immediately
  access_log  /var/log/nginx/hr.access.log main buffer=128k flush=60 if=$log_ua;

  # Rules to work out when cache should/shouldn't be used
  set $skip_cache 0;

  # POST requests and urls with a query string should always go to PHP
  if ($request_method = POST) {
      set $skip_cache 1;
  }   
  if ($query_string != "") {
    set $skip_cache 1;
  }   
  # Don't cache uris containing the following segments. 'admin' is for one of my websites, it's not required
  # for everyone. I've removed index.php as I want pages cached.
  #if ($request_uri ~* "/wp-admin/|/admin-*|/purge*|/xmlrpc.php|wp-.*.php|/feed/|index.php|sitemap(_index)?.xml") {
  if ($request_uri ~* "/wp-admin/|/admin-*|/purge*|/xmlrpc.php|wp-.*.php|/feed/|sitemap(_index)?.xml") {
    set $skip_cache 1;
  }   
  # Don't use the cache for logged in users or recent commenters
  #  if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_no_cache|wordpress_logged_in|code|PHPSESSID") {
  if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wordpress_logged_in|code") {
    set $skip_cache 1;
  }

  # If we skip the cache it's likely customised for one user. Set the caching headers to match.
  # http://www.mobify.com/blog/beginners-guide-to-http-cache-headers/
  if ($skip_cache = 1) {
    set $cacheControl "private, max-age=0, s-maxage=0, no-cache, no-store";
  }
  if ($skip_cache = 0) {
    set $cacheControl "public, max-age=86400, s-maxage=86400";
  }

  # Default location to serve
  location / {
    # If the file can't be found try adding a slash on the end - it might be
    # a directory the client is looking for. Then try the Wordpress blog URL
    # this might send a few requests to PHP that don't need to go that way
    try_files $uri $uri/ /blog/index.php?$args;
    more_clear_headers Server; more_clear_headers "Pragma"; more_clear_headers "Expires";
    # add_header Z_LOCATION "hr_root"; add_header URI $uri; # DEBUG
  }

  # Send HipHop and PHP requests to HHVM
  location ~ \.(hh|php)$ {
    fastcgi_keep_conn on;
    fastcgi_intercept_errors on;
    fastcgi_pass   php;
    include        fastcgi_params;
    fastcgi_param  SCRIPT_FILENAME $document_root$fastcgi_script_name;

    # Use the cache defined above. Cache 200 (success) status's, for 24 hours, and cache
    # specific other status's for an hour. This helps mitigate DDOS attacks.
    # Only cache GET and HEAD requests
    fastcgi_cache HR_CACHE;
    fastcgi_cache_valid 200 1440m;
    fastcgi_cache_valid 403 404 405 410 414 301 302 307 60m;
    add_header X-Cache $upstream_cache_status;

    fastcgi_cache_methods GET HEAD; 
    fastcgi_cache_bypass $skip_cache;
    fastcgi_no_cache $skip_cache;

    # Set the cache control headers we prepared earlier. Remove the old unnecessary Pragma and hide
    # the server version. Clearing existing headers seems necessary
    more_clear_headers "Cache-Control";
    add_header Cache-Control $cacheControl;
    more_clear_headers "Pragma"; more_clear_headers Server; more_clear_headers "Expires";

  }
}

Update 2, putting the two above parts together

fastcgi_cache_path /dev/shm/c1 levels=1:2 keys_zone=C1:50m inactive=1440m;
fastcgi_cache_path /dev/shm/c2 levels=1:2 keys_zone=C2:50m inactive=500m;
fastcgi_cache_path /dev/shm/c3 levels=1:2 keys_zone=C2:50m inactive=10m;

# regular expression for directory app1
location ~* ^\/.*app1\/.*.php$ { 
  fastcgi_cache C1; # etc
}

# regular expression for directory app2
location ~ ^\/.*app2\/.*.php$ {
  fastcgi_cache C2; # etc
}

# regular expression for directory app3
location ~ ^\/.*app3\/.*.php$ {
  fastcgi_cache C3; # etc
}
6
  • But all the locations will always be forwarded to the php location block using try_files $uri $uri/ /index.php?$query_string;, so the cache settings are ignored. Is there any other way?
    – Darksworm
    Apr 14, 2016 at 21:09
  • This is basically copied and pasted from my own Nginx configuration, it works as I've described. I'll post a link to my Nginx tutorial in my answer in a minute, you can download my actual config files to see.
    – Tim
    Apr 14, 2016 at 21:30
  • I haven't found anything similar in your configs, and it is not working for me. The cache only works if i set it in the php location block.
    – Darksworm
    Apr 24, 2016 at 18:54
  • Yes, the cache must be explicitly specified in the PHP location block. I'll copy the whole config file into my answer above, but you should go to the "part one" link above and download the file with the link "Nginx Configuration for PHP and WordPress single site" for the full thing.
    – Tim
    Apr 24, 2016 at 19:34
  • But in this example the cache for 200 will always be valid for 1440 minutes due to fastcgi_cache_valid 200 1440m; which cannot be changed coming from different location blocks. Or am I missing something?
    – Darksworm
    Apr 24, 2016 at 20:54
1

Recently I ran into this limitation again.

To reiterate the problem more clearly: cache multiple different PHP responses for different lengths of time.

I came up with this hack:

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

location ~ /resource/.* {
    try_files $uri $uri/ //index.php?$query_string;
}

location ~ /longresource/.* {
    try_files $uri $uri/ ///index.php?$query_string;
}

location ~ //index\.php$ {
    fastcgi_cache resources_cache;
    fastcgi_cache_valid 200 60m;
    fastcgi_cache_methods GET;

    include snippets/fastcgi-php.conf;
    fastcgi_pass unix:/run/php/php7.0-fpm.sock;
}

location ~ ///index\.php$ {
    fastcgi_cache resources_cache;
    fastcgi_cache_valid 200 180m;
    fastcgi_cache_methods GET;

    include snippets/fastcgi-php.conf;
    fastcgi_pass unix:/run/php/php7.0-fpm.sock;
}

location ~ \.php$ {
    include snippets/fastcgi-php.conf;
    fastcgi_pass unix:/run/php/php7.0-fpm.sock;
}

This code snippet is from a laravel project, where everything goes through the index.php file.

This works because //index.php and /index.php (and the same location with any number of preceding slashes) resolves to the same file, but to a different nginx location block.

This is an utterly terrible hack production tested and not DRY at all, but it works and you can avoid using if.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .