nginx: Specify custom headers in rewritten location blocks

6,315

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;
    }
Share:
6,315
Etienne Perot
Author by

Etienne Perot

https://perot.me/

Updated on September 18, 2022

Comments

  • Etienne Perot
    Etienne Perot almost 2 years

    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
    • sendmoreinfo
      sendmoreinfo over 11 years
      proxy_pass'ing to self works, but that's not really a solution. Perhaps you could use 'alias'?
    • Etienne Perot
      Etienne Perot over 11 years
      Added how the URLs look like in my real config
    • Sameer
      Sameer over 11 years
      can you share the full config
    • Etienne Perot
      Etienne Perot over 11 years
      Added working config and sample directory structure and expected behavior for those files
  • Etienne Perot
    Etienne Perot over 11 years
    Sorry but I don't think you understood the question. I'm not asking about case sensitive/insensitive matching here...
  • Andrei Mikhaltsov
    Andrei Mikhaltsov over 11 years
    I think Sameer is right: just change "location /static" to "location ~ /static"
  • Sameer
    Sameer over 11 years
    You want to append headers based on a condition, so you'll have to match/search for it.
  • Etienne Perot
    Etienne Perot over 11 years
    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.
  • giorgiosironi
    giorgiosironi about 7 years
    Very good, also notice that ?max=1 parameter is not exposed to end users, and can be named as you like.
  • giorgiosironi
    giorgiosironi about 7 years
    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 nginx.com/resources/wiki/start/topics/depth/ifisevil