2

I'm trying to use Nginx page caching instead of Wordpress caching. The caching seems to work fine, but I'm having trouble setting conditional caching headers based on a variable - whether a user is logged into wordpress. If a user is logged in I want no-cache headers applied, if not the page can be cached for a day by both Wordpress and the CDN. I'm finding I can only add one header inside an if statement.

I have read (but not fully understood, because it's late here) [if is evil][1]. I also found an answer on stack exchange (on my laptop, can't find it now) that said inside an if block only one add_header works.

Can anyone give me ideas for an alternative that might work better? I know I can combine the expires with the cache-control, but I want more headers in there, plus I want to understand and learn.

Here's a significantly simplified config with the relevant parts in place.

server {
  server_name example.com;

  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.
  if ($request_uri ~* "/wp-admin/|/admin-*|/xmlrpc.php|wp-.*.php|/feed/|index.php|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") {
    set $skip_cache 1;
  }

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

  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;

    # Cache Stuff
    fastcgi_cache CACHE_NAME;
    fastcgi_cache_valid 200 1440m;
    add_header X-Cache $upstream_cache_status;

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

    add_header Z_ABCD "Test header";

    if ($skip_cache = 1) {
      add_header Cache-Control "private, no-cache, no-store";
      add_header CACHE_STATUS "CACHE NOT USED";
    }
    if ($skip_cache = 0) {
      add_header Cache-Control "public, s-maxage = 240";
      expires 1d;
      add_header CACHE_STATUS "USED CACHE";
    }

    add_header ANOTHER_HEADER "message";
    }
}
1

An alternative to the if directive is the map directive. And assuming the CACHE_STATUS vs CACHE_STATIC is just a typo in your question, you could try this:

map $http_cookie $expires {
    default 1d;
    ~*wordpress_logged_in off;
}
map $http_cookie $control {
    default "public, s-maxage = 240";
    ~*wordpress_logged_in "private, no-cache, no-store";
}
map $http_cookie $status {
    default "USED CACHE";
    ~*wordpress_logged_in "CACHE NOT USED";
}
server {
    ...
    location ~ \.(hh|php)$ {
        ...
        expires $expires;
        add_header Cache-Control $control;
        add_header CACHE_STATUS $status;
    }
}

The map directives should be placed inside the http container (at the same level as the server block) as shown above.

The map directive is documented here.

  • 1
    I would replace $http_cookie to $cookie_SOMETHING but not sure if wordpress_logged_in is a cookie name or value. – Alexey Ten Jan 15 '16 at 12:09
  • @AlexeyTen Unfortunately it is only part of a cookie name. The full name includes user specific data. – Richard Smith Jan 15 '16 at 12:23
  • Thanks @Richard Smith, an interesting option. I've updated the original question to include all rules around when to cache and when not to - there's four of them, and I may have to add more. I didn't think removing the additional rules would make a difference to the answer, but it appears it does. Given the large number of conditions is this solution still practical / possible? – Tim Jan 15 '16 at 18:01
0

I've come up with one solution myself, based on the answer @Richard Smith provided that didn't quite do what I needed. I've used the more cache-control header rather and dropped the unnecessary expires directive.

This sits inside the server block

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";
}

Then this goes inside every applicable location block

add_header Cache-Control $cacheControl;

This means no "if" is required inside the location block. I think this solves the problem, but I'm still interested if anyone has alternative ideas.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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