Add expires header conditionally based on mime type in nginx

Solution 1:

As of nginx 1.7.9:

map $sent_http_content_type $expires {
  default         off;
  application/pdf 42d;
  ~image/         max;
}

expires $expires;

NOTE: $sent_http_content_type is valid, but it cannot be accessed until the server has processed the request...

The answer is pointed at the map entry for application/pdf which allows a date value, or could even be application/pdf modified +42d for example

Solution 2:

The if directive in Nginx is processed early on at the rewrite stage, and hence the $sent_http_content_type variable has not yet been initialised - see this nginx bug report for details.

EDIT: Comparing usernames, that bug report might well be you, actually! (^_^)

Similarly, the expires directive doesn't seem to support variables in the same way that, for example, add_header does. So, since you're not in a position to statically specify the location based on file extensions, I can only think of two basic approaches.

One would be to use the map and add_header approach suggested by Vadim above to manually set HTTP headers instead of allowing the expires directive to do it. This is rather less flexible as it won't set the Expires header, but I'd hope that any browser these days would do the right thing with just Cache-Control being set. Here's an example that I've briefly tested:

map $sent_http_content_type $cacheable_types {
    "text/css"    "max-age=864000";
    "image/jpeg"  "max-age=864000";
    default       "";
}

# ...

server {
    # ...
    location / {
        # ...
        add_header "Cache-Control" $cacheable_types;
    }
}

The value 864000 is 10 days in seconds - you'll have to change this to whatever you wish. The advantage of this approach is that you can specify different times for each file type, and even override other aspects of the Cache-Control header - you discussion of this header here, and the official part from the HTTP RFC here if you prefer something more formal.

The second approach would be to just arrange that requests that will result in cacheable content all come under a specific path that you can use in a location directive like this:

location / {
    # ...
    location /static {
        expires 10d;
    }
}

This makes the nginx configuration easier because you can use its builtin expires directive, but whether it's an option very much depends whether you can enforce that pattern of URLs on your code.