maxuru ~ % cat haproxy/security-headers
Add security headers in HAProxy
/etc/haproxy/haproxy.cfgfrontend web http-response set-header Strict-Transport-Security "max-age=31536000; includeSubDomains" http-response set-header X-Content-Type-Options nosniff http-response set-header X-Frame-Options DENY http-response set-header Referrer-Policy strict-origin-when-cross-origin
# Deliberately absent: X-XSS-Protectionset-header, not add-header
Section titled “set-header, not add-header”The directive HAProxy suggests when it rejects rspadd is
http-response add-header — and for security headers that is the wrong half of
the pair.
add-headerappends a header, even if one with that name already exists.set-headerreplaces any existing value, leaving exactly one.
If a backend already emits X-Frame-Options and HAProxy appends a second one,
the response carries two. Browser behaviour when headers conflict is not
something to rely on, and for Content-Security-Policy in particular, multiple
headers are combined by intersection — each additional policy can only further
restrict, which produces confusing breakage that looks like a CSP bug.
Use set-header and the proxy’s value wins cleanly. See
removed directives for the rest of the 2.1
migration.
HAProxy does not have nginx’s inheritance trap
Section titled “HAProxy does not have nginx’s inheritance trap”Worth saying plainly, because if you came from nginx you may be bracing for it.
In nginx, a single add_header inside a location silently discards every
inherited security header. HAProxy has no equivalent behaviour: http-response
rules are a list, they are evaluated in order, and adding one in a backend does
not delete the ones set in the frontend.
The ordering rule to know is simply that frontend response rules run after
backend response rules for a given response, so a set-header in the frontend
overrides one in the backend. That is usually the behaviour you want — the edge
has the final say.
Skip X-XSS-Protection
Section titled “Skip X-XSS-Protection”Old HAProxy hardening guides include it, generally as rspadd X-XSS-Protection:\ 1;\ mode=block. Do not carry it forward.
The header controlled a browser XSS filter that has been removed from every current browser, and while it existed it was demonstrably harmful: attackers could craft URLs that made the filter misidentify legitimate scripts and selectively disable them, creating a vulnerability on pages that had none. MDN recommends against it without qualification.
Content-Security-Policy is what actually addresses XSS. It needs to be written
against the specific application rather than pasted, which is why it is not in
the block above — a CSP copied from a guide either breaks the site or is so
permissive it does nothing. Set it once you can enumerate your own script
sources, and use Content-Security-Policy-Report-Only first.
The same reasoning appears on the nginx and Apache pages; it is a property of the header, not of the server.
HSTS at the edge
Section titled “HSTS at the edge”HSTS belongs on the load balancer, because the load balancer is what terminates TLS and therefore what the browser is talking to. Two cautions that apply wherever it is set:
includeSubDomainscovers every subdomain, including ones not yet using HTTPS. Check them before adding it.max-age=31536000is a year, and it is enforced by the browser, not by you — a client that has seen the header will refuse plain HTTP for that domain until it expires. There is no fast way to undo it.
Start with a short max-age, confirm nothing breaks, then raise it.
Related
Section titled “Related”- HAProxy reqadd, rspadd and reqirep were removed — the syntax these replaced.
- nginx security headers — the same headers, and the inheritance trap HAProxy avoids.