How to Map Multiple Headers to the Same Variable in Nginx
May 2020
The nginx map module is a nifty tool that allows you to programmatically change behavior based on things like http headers that come in.
In this post, I'll show how to choose a different file to serve based on a custom header that comes in, then how to check multiple headers to make a final decision on where to go.
Setting up the Playground
I'm going to use docker compose to walk us through this. If you make a docker-compose.yml file and set it up like so:
version: "3.3"
services:
nginx:
image: nginx:latest
ports:
- 9000:80
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf
- ./nginx/primary.html:/usr/share/nginx/html/primary.html
- ./nginx/secondary.html:/usr/share/nginx/html/secondary.html
We'll then create an nginx directory and throw four files in it: nginx.conf, default.conf, primary.html, and secondary.html.
nginx.conf:
user nginx;
worker_processes 1;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
#gzip on;
include /etc/nginx/conf.d/*.conf;
}
default.conf:
server {
listen 80;
server_name localhost;
#charset koi8-r;
#access_log /var/log/nginx/host.access.log main;
root /usr/share/nginx/html;
location / {
try_files '' /primary.html =404;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
primary.html
<h1>The primary html page</h1>
secondary.html
<h1>The secondary, as in NOT primary, html page</h1>
Working with maps
Right now, if you run:
docker-compose up
You can hit any endpoint on localhost:9000 and get back the primary.html file. E.g.
$ curl localhost:9000/one
<h1>The primary html page</h1>
$ curl localhost:9000/two
<h1>The primary html page</h1>
What if we want to serve up the secondary html page based only on a certain custom header coming in? Well, then we can use variables. If we modify:
location / {
try_files '' /$actual_variable =404;
}
Then we can use a map variable to serve up whatever file we want. Place this block of code outside the server block and restart the docker container:
map $http_x_new_header $actual_variable {
~secondary "secondary.html";
default "primary.html";
}
Then you can still hit any endpoint like normal and get the primary.html page, but you can also specify a our x-new-header with the value of "secondary" and get the secondary.html page:
$ curl localhost:9000/something
<h1>The primary html page</h1>
$ curl -H "X-New-Header: secondary" localhost:9000/something
<h1>The secondary, as in NOT primary, html page</h1>
Pretty interesting. But one follow up question: what if we want two different headers to determine the outcome of this variable. For example, what if we have some legacy clients calling us with a legacy header, and we want to check the value of the legacy header as well as the new header? Well, we can nest maps:
map $http_x_legacy_header $default_variable {
~secondary "secondary.html";
default "primary.html";
}
map $http_x_new_header $actual_variable {
~secondary "secondary.html";
default $default_variable;
}
If you restart nginx now (docker-compose down && docker-compose up -d), you can see it in action:
# regular call
$ curl localhost:9000/endpoint
<h1>The primary html page</h1>
# using the new header like before
$ curl -H "X-New-Header: secondary" localhost:9000/something
<h1>The secondary, as in NOT primary, html page</h1>
# using the legacy header
$ curl -H "X-Legacy-Header: secondary" localhost:9000/something
<h1>The secondary, as in NOT primary, html page</h1>
# using both headers cause why not
$ curl -H "X-Legacy-Header: secondary" -H "X-New-Header: secondary" localhost:9000/something~
<h1>The secondary, as in NOT primary, html page</h1>
# notice what happens when the value is changed
$ curl -H "X-Legacy-Header: idk" -H "X-New-Header: huh" localhost:9000/something~
<h1>The primary html page</h1>
Nick Fisher is a software engineer in the Pacific Northwest. He focuses on building highly scalable and maintainable backend systems.