5

I'm trying to set some headers only for specific location blocks in nginx.

The problem I have is that those location blocks contain rewrite statements, which apparently seem to drop the custom headers.

In this example, I have two rules I want:

  • Files inside /static should have expires max; (which sets the headers Cache-Control: max-age=some huge value and Expires: some future date really far off) and have their names be rewritten to something that doesn't contain /static
  • Files everywhere else should have Cache-Control: public (no max-age)

Here's the configuration I tried:

server {
    listen [::]:80;
    root /somepath;
    location /static {
        expires max;
        rewrite /static(.*) /whatever$1;
    }
    add_header Cache-Control public;
}

And having the following directory structure:

/somepath
/somepath/f1.txt
/somepath/static/f2.txt

Then we get the following:

  • f1.txt: Cache-Control: public, no Expires header
  • f2.txt: Cache-Control: public, no Expires header

That's valid for f1.txt but not f2.txt. I want it to be like this:

  • f1.txt: Cache-Control: public, no Expires header
  • f2.txt: Cache-Control: max-age=some huge value, Expires: some future date really far off

The problem, I think, stems from the rewrite /static(.*) /whatever$1; line, which makes nginx cancel the headers it has added so far and then add them again (thus re-adding Cache-Control). As such, a trivial workaround would be this:

server {
    listen [::]:80;
    root /somepath;
    location /static {
        rewrite /static(.*) /whatever$1;
    }
    location /whatever {
        expires max;
    }
    add_header Cache-Control public;
}

The problem is that in my real config file, the rewrite isn't as friendly-looking as that. The rewritten URL is not easily matchable in a way that wouldn't also match some files that shouldn't have expires max, so I can't really use this workaround.

Is there a way to make those headers stick after a rewrite?

EDIT: Here's what my real URLs look like:

location ~ /(?:posts-)?img/.*-res- {
    access_log               off;
    expires                  max;
    rewrite                  "/img/(.*)-res-.{8}(.*)" /img/$1$2;
    rewrite                  "/posts-img/(.*)-res-.{8}(.*)" /posts/$1$2;
}

While I can add a location block for /img which would take care of files rewritten using the first rewrite rule, I cannot add one for the second one (/posts) because some files in /posts are not cacheable resources and thus shouldn't have expires max.

EDIT 2: Full config (or at least containing all the relevant parts):

server {
    listen [::]:80;
    root /somepath;
    server_name domain.tld;
    location ~ /(?:posts-)?img/.*-res- {
        access_log               off;
        expires                  max;
        rewrite                  "/img/(.*)-res-.{8}(.*)" /img/$1$2;
        rewrite                  "/posts-img/(.*)-res-.{8}(.*)" /posts/$1$2;
    }
    add_header Cache-Control public;
}

Directory structure:

/somepath
/somepath/img/f1.png
/somepath/posts/post1.html
/somepath/posts/d1/f2.png
/somepath/posts/d2/f2.png

Expected behavior according to HTTP request:

  • GET /somepath: Serves /somepath with Cache-Control: public
  • GET /somepath/img/f1.png: Serves /somepath/img/f1.png with Cache-Control: public
  • GET /somepath/img/f1-res-whatever.png: Serves /somepath/img/f1.png with the headers sent by expires max
  • GET /somepath/posts/post1.html: Serves /somepath/posts/post1.html with Cache-Control: public
  • GET /somepath/posts/d1/f2.png: Serves /somepath/posts/d1/f2.png with Cache-Control: public
  • GET /somepath/posts-img/d1/f2-res-whatever.png: Serves /somepath/posts/d1/f2.png with the headers sent by expires max
sysadmin1138
  • 133,124
  • 18
  • 176
  • 300
Etienne Perot
  • 103
  • 1
  • 8

2 Answers2

2

This should work (I verified this with somewhat simpler config, though). Igor Sysoev recommends to use regex locations as little as possible, by the way.

    location /img {
        if ($arg_max) { expires max; }
        ...
    }

    location /posts-img {
        if ($arg_max) { expires max; }
        ...
    }

    location ~ /(?:posts-)?img/.*-res- {
        access_log               off;
        expires                  max;
        rewrite                  "/img/(.*)-res-.{8}(.*)" /img/$1$2?max=1;
        rewrite                  "/posts-img/(.*)-res-.{8}(.*)" /posts/$1$2?max=1;
    }
sendmoreinfo
  • 1,772
  • 13
  • 34
  • Very good, also notice that `?max=1` parameter is not exposed to end users, and can be named as you like. – giorgiosironi Apr 28 '17 at 16:13
  • However, be wary that if you have other headers set in the first and second `location` blocks, they won't work... along with many other cases; `if` is listed as unsafe https://www.nginx.com/resources/wiki/start/topics/depth/ifisevil/ – giorgiosironi Apr 28 '17 at 16:19
0

For case insensitive location matching.

location ~* /static/

case insensitive remove the " ***** "

location ~* /static/

Source Nginx location directive documentation

Sameer
  • 4,118
  • 2
  • 17
  • 11
  • Sorry but I don't think you understood the question. I'm not asking about case sensitive/insensitive matching here... – Etienne Perot Dec 24 '12 at 03:45
  • I think Sameer is right: just change "location /static" to "location ~ /static" – Andrei Mikhaltsov Dec 25 '12 at 16:41
  • You want to append headers based on a condition, so you'll have to match/search for it. – Sameer Dec 25 '12 at 20:13
  • The "append header based on a condition" part works, the original URL (`/static/something`) is matched. What doesn't work is that the added headers do not stick after the `rewrite` rule. Additionally, because the rewritten URL doesn't match `/static` (so `location ~ /static` won't catch it), the header isn't added again after the rewrite. – Etienne Perot Dec 25 '12 at 20:41