0

My goal is to cache assets with query strings with a certain policy and assets that don't have query strings with another. Unfortunately, nginx ignores query strings in location blocks, so I'm stuck using the if/error_page strategy helpfully suggested here:

location /static/ {
    alias /home/client/public/;
    error_page 418 = @long_lived_caching_strategy;

    if ($query_string = "example=querystring") {
      return 418;
    }
  }

  location @long_lived_caching_strategy {
    etag off;
    add_header Cache-Control "public";
    expires 1y;
  }
}

However, my error logs show me that in the above configuration, the alias directive is ignored. But when I try to move the alias directive into the @long_lived_caching_strategy, nginx tells me that alias isn't allowed there!

What are my options for a workaround?

Alternatively, is there another way to set etag and add_header directives differently depending on whether the URL has a query string?

Jason Benn
  • 73
  • 1
  • 2
  • 7
  • Do you really send "418 I'm a teapot" often enough to bother with findng a complicated way to add error caching? If a proxy cache gets this, and you later use that URL, it could be a year before anyone sees content. – Tim Apr 09 '17 at 02:46
  • You were completely right, I thought that I could use return/error_page to divert requests with query strings to a named location block, but that came with the side effect of a 419 response code. Duh. – Jason Benn Apr 09 '17 at 16:35

2 Answers2

1

I found an easier solution to my problem thanks to this thread. My goal was really to conditionally add caching-related headers based on whether or not there was a query string, and using if to set strings turned out to be the easiest way to achieve that. Here's my final configuration:

  location /static {
    alias /usr/local/etc/nginx/relative-paths/picasso/client/public;

    # If an asset has a query string, it's probably using that query string for cache busting purposes.
    set $long_lived_caching_strategy 0;
    if ($query_string) { set $long_lived_caching_strategy 1; }

    if ($long_lived_caching_strategy = 0) {
      # Cache strategy: caches can store this but must revalidate the content with the server using ETags before serving a 304 Not Modified.
      set $cache_control "no-cache";
    }

    if ($long_lived_caching_strategy = 1) {
      # Cache strategy: proxy caches can store this content, and it's valid for a long time.
      set $cache_control "public, max-age=86400, s-maxage=86400";  # 1 day in seconds
    }

    add_header Cache-Control $cache_control;

    # Some types of assets, even when requested with query params, are probably not going to change and should be cacheable by anyone for a long time.
    location ~* \.(?:jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc|ttf)$ {
      add_header Cache-Control "public max-age=86400, s-maxage=86400";
      etag off;
      access_log off;
    }
  }
Jason Benn
  • 73
  • 1
  • 2
  • 7
-1

You can do this manually, if you have the headers_more module compiled into Nginx, for another way to make add_header available. Some distributions will include this, you may have to build Nginx from source - which is surprisingly quick and easy.

add_header Cache-Control "public, max-age=691200, s-maxage=691200";

You don't need to use the expires header - reason is here.

Tim
  • 31,888
  • 7
  • 52
  • 78
  • Good to know! But I would prefer to avoid any complexity in the installation process, as that would involve updating not just my nginx config, but also my deploy process. – Jason Benn Apr 09 '17 at 16:38
  • It's easy to test if add_header always works on your Nginx distribution using "curl -i" to show response headers. It's not compiled by default in Nginx, but some distributions will include it. Amazon Linux's version doesn't, but there are distributions that do. – Tim Apr 09 '17 at 18:57