Nginx – Add header to every request for a sub directory

configurationhttp-headersnginx

I've got a server configuration for a PHP application which has a general X-Frame-Options header set to "SAMEORIGIN".

Except part of the application should be allowed to be included into an Iframe. Conveniently these are all served from https://example.app/external/* So I want to add a different X-Frame-Options header (or omit it) for request to this directory.

I'm now struggeling with this. To test things I added a custom header to 2 places in my server {} config:

server {
  server_name example.app;
  listen 443 ssl;
  root /data/example.app/public;

  add_header X-XSS-Protection "1; mode=block";
  add_header X-Content-Type-Options "nosniff";

  # this one gets added to every request
  add_header X-Test-Header "hello world";

  index index.html index.php;

  charset utf-8;

  location /external/ {
    # this one doesn't show up for https://example.app/external/show/24
    add_header X-Test-Header-2 "Why not";
    try_files $uri $uri/ /index.php?$query_string;
  }

  location / {
    try_files $uri $uri/ /index.php?$query_string;
  } 


  location ~ \.php$ {
    fastcgi_pass unix:/var/run/php/php7.2-fpm.sock;
    fastcgi_index index.php;
    fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
    include fastcgi_params;
  }

  location ~ /\.(?!well-known).* {
    deny all;
  }  

  # ssl config
  # ...
  # gip config
  # ...

}

As you can see I added two test headers to my config file. The first shows up; but the one specifically for the subdirectory doesn't get sent with the response. I think it has to do with the match of the location block so I tried these

location /external/ {}
location = /external/ {}
location ~* /external/ {}
location ^~ /external/ {}

But all requests (to eg example.app/external/nl/something/45) only have the X-Test-Header and not the X-Test-Header-2.

Any idea why?

Best Answer

The add_header directive cannot be added to a location without preventing all of the other add_header directives from taking effect. See the documentation, particularly:

These directives are inherited from the previous level if and only if there are no add_header directives defined on the current level.

Also, the location block you added is not where the URI is finally processed. The try_files directive will internally rewrite the URI to /index.php which will cause Nginx to process the request using the location ~ \.php$ block.

You can use an add_header statement with a variable, and set that variable using a map. Place the statement in the same block as your other add_header statements. See this document for details.

For example:

map $request_uri $myheader {
    default           "Default Value";
    ~^/external/      "External Value";
}

server {
    ...
    add_header X-XSS-Protection       "1; mode=block";
    add_header X-Content-Type-Options "nosniff";
    add_header X-Test-Header          $myheader;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }
    location ~ \.php$ { 
        ... 
    }
}

Also, if you set the value of the variable to "", then the header is silently discarded.