Security response headers
Security headers are edge policy and this chart deliberately does not set them on the pod. They belong on the ingress, next to TLS termination and any WAF: changing a header there needs no image rebuild and no pod restart, and Strict-Transport-Security can only be set meaningfully by whatever terminates TLS. The pod's own NGINX cannot serve as a single source of truth anyway — add_header is not inherited into a location that defines any add_header of its own, and the base image's asset locations do exactly that.
Set them through ingress.annotations, which is passed through verbatim. A policy worth starting from:
Content-Security-Policy: frame-ancestors 'self'; base-uri 'self'; form-action 'self'; object-src 'none'
Referrer-Policy: strict-origin-when-cross-origin
Strict-Transport-Security: max-age=31536000; includeSubDomainsThose four CSP directives block clickjacking, <base> tag injection, form exfiltration and plugin-object abuse without touching script loading, so they do not break a normal WordPress site. Note what is missing: a script-src without 'unsafe-inline' is the directive that actually contains XSS, and WordPress cannot satisfy it without nonces on every inline <script> — theme or mu-plugin work, not something the ingress can add. A CSP that keeps 'unsafe-inline' looks like protection in a scanner report and provides none, so it is better to ship the four directives above honestly than a permissive default-src line.
Traefik (ingressClassName: traefik or nginx-traefik) — a Middleware holding the headers, referenced from the Ingress. customResponseHeaders replaces a header rather than appending to it, so this also cleanly overrides the Referrer-Policy the base image emits:
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: wordpress-security-headers
namespace: my-namespace
spec:
headers:
customResponseHeaders:
Content-Security-Policy: "frame-ancestors 'self'; base-uri 'self'; form-action 'self'; object-src 'none'"
Referrer-Policy: "strict-origin-when-cross-origin"
stsSeconds: 31536000
stsIncludeSubdomains: trueingress:
annotations:
traefik.ingress.kubernetes.io/router.middlewares: my-namespace-wordpress-security-headers@kubernetescrdingress-nginx (ingressClassName: nginx) — a configuration snippet. more_set_headers replaces rather than appends, same as above:
ingress:
annotations:
nginx.ingress.kubernetes.io/configuration-snippet: |
more_set_headers "Content-Security-Policy: frame-ancestors 'self'; base-uri 'self'; form-action 'self'; object-src 'none'";
more_set_headers "Referrer-Policy: strict-origin-when-cross-origin";This requires allow-snippet-annotations: true in the ingress-nginx controller ConfigMap. The controller has defaulted it to false since v1.9, and it is a cluster-wide setting owned by the controller, not by this chart — so on a hardened ingress-nginx the per-site route is closed and the only built-in alternative is the controller-global add-headers ConfigMap, which applies the same headers to every site behind that controller.