14

Running nginx 1.4.1 on ubuntu 12.10

Need to send expires header conditionally and based on mime type/content type of http response.

Added this simple piece of code inside location / {

if ($sent_http_content_type = "text/css") { 
expires 7d;
}

The expires header is not sent even if $sent_http_content_type contains "text/css"

How to fix this ?

Checking file extensions is not enough, because in my app js,css, images are being generated dynamically from php. So need to check the mime and add headers based on that too.

3
  • Why not just match the suffix of the file name? Jun 13, 2013 at 15:55
  • @jeffatrackaid in my application js,css, images are generated dynamically from php scripts, which wont match file extension, hence i need to add expires and other headers with mime checking. Jun 14, 2013 at 3:02
  • Why would you not set the expires header in the PHP script itself? That's the right layer to do it, especially if it can change based on the type of request and content. For example, some CSS might be give an 1 hour lifetime, but others might get only 10 minutes. Your application code knows this information at runtime, nginx cannot see inside your business logic.
    – rmalayter
    Jun 14, 2013 at 16:44

4 Answers 4

21

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

2
  • 1
    For newer NGINX installations, this is the desired answer.
    – Oliver
    May 31, 2017 at 21:00
  • This example and more details, such as time syntax, can be found in the Nginx docs.
    – Drakes
    Feb 25, 2019 at 15:48
10

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.

1
  • See my comment on Vadims... had an idea...
    – marksyzm
    Oct 22, 2013 at 23:01
3

$sent_http_content_type is invalid. Request header values can be accessed via $http_name. Also, for Content-type header nginx has embedded variable $content_type.

If you would like to check $content_type against bunch of types, it would be better to use map:

   map $content_type $test_header {
        "text/css"  OK;
        "text/html" OK;
        ...
        default "";
   }

   server {
        location / {
             add_header "X-Test" $test_header;
        }
   }

If $test_header evaluates to empty string, header will not be set.

For futher information, see: http://nginx.org/en/docs/http/ngx_http_core_module.html#variables http://nginx.org/en/docs/http/ngx_http_map_module.html http://nginx.org/en/docs/http/ngx_http_headers_module.html#add_header

3
  • thanks for that, it can be used to add a header, but I am looking for more, need to use expires, based on mime type Jun 13, 2013 at 12:32
  • Do you think you could put the expires value as per each $content_type? I.e. the map could use a $content_type value of 30d or 1y and then you could place that in location / with expires $test_header?
    – marksyzm
    Oct 22, 2013 at 22:42
  • Ignore that... expires doesn't support it. However you could test against an if statement and place a hard value in expires... I'm currently having a go at that
    – marksyzm
    Oct 22, 2013 at 23:01
2

If you must use the mime-type, then try:

if ($content_type ~= "text/css") { 
expires 7d;
}

However, you may want to consider something like this:

location ~ \.(css|js|htc)$ {
    add_header Pragma "public";
    add_header Cache-Control "max-age=31536000, public, must-revalidate, proxy-revalidate";
         log_not_found off;
         access_log off;

}
location ~ \.(html|htm|rtf|rtx|svg|svgz|txt|xsd|xsl|xml)$ {
    add_header Pragma "public";
    add_header Cache-Control "max-age=3600, public, must-revalidate, proxy-revalidate";
         log_not_found off;
        access_log off;

}
location ~ \.(asf|asx|wax|wmv|wmx|avi|bmp|class|divx|doc|docx|eot|exe|gif|gz|gzip|ico|jpg|jpeg|jpe|mdb|mid|midi|mov|qt|mp3|m4a|mp4|m4v|mpeg|mpg|mp
e|mpp|otf|odb|odc|odf|odg|odp|ods|odt|ogg|pdf|png|pot|pps|ppt|pptx|ra|ram|svg|svgz|swf|tar|tif|tiff|ttf|ttc|wav|wma|wri|xla|xls|xlsx|xlt|xlw|zip)$
 {
    add_header Pragma "public";
    add_header Cache-Control "max-age=31536000, public, must-revalidate, proxy-revalidate";
         log_not_found off;
         access_log off;

}

We use this on a very busy site with very good effect.

1
  • i cannot test on file extension. in my application js,css and images are generated on the fly from php scripts. hence i need some mime based checking and enable expires. Jun 14, 2013 at 3:01

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .