Nginx – Alternative to ‘If’ Inside Location Blocks for Caching Headers

nginx

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

Best Answer

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.