Skip to content

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; includeSubDomains

Those 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:

yaml
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: true
yaml
ingress:
  annotations:
    traefik.ingress.kubernetes.io/router.middlewares: my-namespace-wordpress-security-headers@kubernetescrd

ingress-nginx (ingressClassName: nginx) — a configuration snippet. more_set_headers replaces rather than appends, same as above:

yaml
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.

Distributed under the Apache-2.0 license. WordPress, NGINX, Bitnami and other names are trademarks of their owners; this project is not affiliated with or endorsed by them.