Nginx: Specify custom headers in rewritten location blocks

cacheconfigurationhttphttp-headersnginx

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

Best Answer

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;
    }