Nginx : Alternative to if inside location blocks for caching headers based on a variable
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";
}
}
Solution 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.