"Source code security plugin" by Christiaan Colen; https://flic.kr/p/xp2RBy

nginx header inconsistency, aka setting headers all the way down

For the three visitors I attract in a month, I’ve had an outsized interest in making this the most secure WordPress site that I can. My focus of late has been primarily on the security-related headers I can set. In particular, ensuring that HSTS and HPKP were present on all requests became a priority.

Why?

A few weeks ago, I noticed that certain assets served from my CDN host lacked the Strict Transport Security headers (HSTS) I’d expected. To the best of my knowledge, I’d configured nginx to set these headers on every request.

Further testing showed that, seemingly randomly, multiple of the headers I’d set would disappear. A lot of searching led me to an insightful response on serverfault:

This is expected behaviour. The add_header directives, much like all other array-type directives in nginx, are inherited from the previous level if and only if there are no add_header directives defined on the current level.

What this meant for me is that as I’d introduced add_header calls in nginx location blocks, specifically as part of enabling microcaching, I’d inadvertently blocked my previously-set HSTS headers, among others.

A Solution?

Unfortunately or not, nginx interprets all include directives when the daemon starts, which means run-time includes aren’t possible. Practically for me, this posed a problem ensuring that domain-specific headers were set, namely those pertaining to “header public key pinning” (HPKP).

My solution, short of using a tool such as Puppet or Chef to build singular, comprehensive configurations, was to break the nginx config into many, smaller includes. Generally:

server {
	server_name *.ethitter.com;
	# SSL and common config
	include common/ssl-ethitter.com.conf;
	# HPKP
	include headers/hpkp-ethitter.com.conf;
	# Start common WP config, concatenated to support HPKP
	include common/dynamic.network.000-general-logs-errors.conf;
	# Static asset concatenation/minification
	location /_static/ {
		include headers/hpkp-ethitter.com.conf;
		include common/dynamic.network.001-static.conf;
	}
	include common/dynamic.network.001-redirects.conf;
	include common/dynamic.network.005-wp-common.conf;
	include common/dynamic.network.006-microcaching-init.conf;
	# Aggressive caching for static files
	location ~* \.(css|js|asf|asx|wax|wmv|wmx|avi|bmp|class|divx|doc|docx|eot|exe|gif|gz|gzip|ico|jpg|jpeg|jpe|mdb|mid|midi|mov|qt|mp3|m4a|mp4|m4v|mpeg|mpg|mpe|mpp|odb|od|odf|odg|odp|ods|odt|ogg|ogv|otf|pdf|png|pot|pps|ppt|pptx|ra|ram|svg|svgz|swf|tar|t?gz|tif|tiff|ttf|wav|webm|wma|woff|wri|xla|xls|xlsx|xlt|xlw|zip)$ {
		include headers/hpkp-ethitter.com.conf;
		include common/dynamic.network.010-assets.conf;
	}
	# pass PHP scripts to Fastcgi listening on Unix socket
	location ~* (^(?!(?:(?!(php|inc)).)*/uploads/).*?(php)) {
		include headers/hpkp-ethitter.com.conf;
		include common/dynamic.network.010-php.conf;
	}
	include common/dynamic.network.999-general.conf;
}

common/ssl-ethitter.com.conf contains:

ssl_certificate /***/ssl/ethitter.com.crt;
ssl_certificate_key /***/ssl/ethitter.com.key;
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /***/ssl/startssl-ocsp-bundle-class2-2015.pem;

headers/hpkp-ethitter.com.conf contains:

add_header Public-Key-Pins 'pin-sha256="65iQO/puStAILaWdxgntiwLqG071dd1b3Qpx4Qwf7z4="; pin-sha256="xm0m8qsyYVIUdaavshcUna6M3p/HZ09ShxBDzseOCZ0="; max-age=5184000; includeSubDomains' always;

The remaining included files contain nothing particularly interesting that isn’t common to serving WordPress (or most PHP applications) via nginx.

My Point

The important detail is that every location block is re-defined within every server block, using shared files via include. This allows me to drop arbitrary changes into each location block, while also maintaining any headers set previously. It also, sadly, requires duplicating location blocks amongst server blocks that only differ by the domains supported in their SSL certificates.

In my particular case, I can include the relevant HPKP declaration for the domain given in server_name, avoiding any crazy SSL errors. Including the various headers in every block also ensures that HSTS and certain security headers are always present. The amount of duplication is acceptable, in my opinion, given the secure headers this configuration consistently sets.

I’m especially pleased that these headers carry through on assets served via my CDN, KeyCDN.

In Short…

nginx doesn’t inherit headers at the current level if add_headers is called at the same level, so plan accordingly and consider include directives.


I’m a vim user, despite the header image. Nano’s okay. 😜

3 thoughts on “nginx header inconsistency, aka setting headers all the way down”

Comments are closed.