When working with reverse proxy configurations in Nginx, we often need to conditionally modify headers based on upstream server responses. A common use case involves HTTP Strict Transport Security (HSTS) headers, where we want to:
1. Default to adding HSTS headers
2. Allow upstream servers to override this behavior
3. Avoid header duplication
The naive approach using if
doesn't work because:
- Nginx processes
if
directives during the rewrite phase - Upstream headers aren't available until later processing stages
add_header
directives inif
blocks often get ignored
The most reliable approach uses Nginx's map
to create conditional logic:
http {
map $upstream_http_strict_transport_security $hsts_header {
default "max-age=15552000";
"" "max-age=15552000";
"~*" $upstream_http_strict_transport_security;
}
server {
location / {
proxy_pass http://webservers;
add_header Strict-Transport-Security $hsts_header;
# Other proxy settings
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
}
}
}
For more complex scenarios, Lua scripting offers greater flexibility:
location / {
proxy_pass http://webservers;
header_filter_by_lua_block {
if not ngx.header["Strict-Transport-Security"] then
ngx.header["Strict-Transport-Security"] = "max-age=15552000"
end
}
}
Verify your configuration with:
curl -I https://yourdomain.com
Key test cases:
- Upstream with no HSTS header → Should add default
- Upstream with HSTS header → Should preserve original
- Upstream with max-age=0 → Should pass through unchanged
The map
solution has minimal overhead as it:
- Executes during configuration load
- Uses simple pattern matching
- Doesn't require additional modules
When working with Nginx reverse proxy configurations, there are cases where you need to conditionally add response headers based on whether the upstream server has already set them. A common use case involves HTTP Strict Transport Security (HSTS) headers where:
- Most sites should have HSTS enabled by default
- Specific upstream servers might need to opt-out
- The proxy should respect upstream's header decisions
The naive solution using if
blocks doesn't work because:
if ($upstream_http_strict_transport_security = "") {
add_header Strict-Transport-Security "max-age=15552000";
}
Nginx's header processing happens before the if
evaluation due to its internal processing phases. The add_header
directive inside if
gets ignored.
Method 1: Using map Directive
This is the most elegant solution that avoids if
entirely:
map $upstream_http_strict_transport_security $hsts_header {
"" "max-age=15552000";
default $upstream_http_strict_transport_security;
}
server {
location / {
proxy_pass http://webservers;
# ... other proxy settings ...
add_header Strict-Transport-Security $hsts_header;
}
}
Method 2: Using a Variable with Default Value
For simpler cases where you just need a default:
set $hsts_header $upstream_http_strict_transport_security;
if ($hsts_header = "") {
set $hsts_header "max-age=15552000";
}
location / {
proxy_pass http://webservers;
add_header Strict-Transport-Security $hsts_header;
}
When implementing conditional headers:
- Test with
curl -I
to verify header behavior - Remember that
add_header
directives inherit only if not present in the current level - Consider using
always
parameter if you need headers in error responses
The same pattern works for other headers like CORS:
map $upstream_http_access_control_allow_origin $cors_header {
"" $http_origin;
default $upstream_http_access_control_allow_origin;
}
location /api/ {
proxy_pass http://backend;
add_header Access-Control-Allow-Origin $cors_header;
add_header Access-Control-Allow-Credentials "true";
}