Security headers are not a substitute for secure code, but they make the browser enforce useful boundaries. The policy should describe the page that is actually served, not a generic template copied from another project.

The actual HTML, local stylesheet and favicon determine CSP resource allowances. Content-Security-Policy enforces; Report-Only reveals violations and needs separate reporting setup. Deliver CSP as a header: frame-ancestors is ignored in meta and has no default-src fallback, form-action needs its own directive, and style-src self alone does not allow inline styles. HSTS is learned from valid HTTPS responses; includeSubDomains at www.example.test covers that host and descendants, not example.test or api.example.test. Check normal, missing and asset responses and verify enforcement in a real browser. No browser test is claimed.
Treat headers as a deployment contract: match actual resources, distinguish enforced and report-only policy, and verify exact host scope and browser behavior.

Start from the resource graph

If a site serves HTML, a local stylesheet, a favicon, and no executable JavaScript, a narrow policy can be understandable:

Content-Security-Policy: default-src 'self'; base-uri 'self'; form-action 'none'; frame-ancestors 'none'; object-src 'none'; script-src 'none'; style-src 'self'; img-src 'self' data:

The script-src 'none' choice is only correct while the site does not require executable scripts. Adding search, comments, or an analytics tag changes the resource graph and should trigger a deliberate policy review.

Add transport and framing protections

For an HTTPS-only site, the following headers are common starting points:

Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=(), usb=()

Only enable HSTS with includeSubDomains when every subdomain is prepared to serve HTTPS. A header is a contract with future deployments, not merely a score in a scanner.

Check behavior, not just presence

Request a normal page, a missing page, and a static asset. Error responses should carry the same important security headers as successful responses. Also confirm that compression and caching do not change the content type or expose an internal path.

The Caddy example in this site is a starting point, not a universal policy. Adapt the host names, certificate handling, and application routes to your own deployment.

Deliver the CSP as a response header. frame-ancestors is ignored in a meta policy and does not inherit default-src; form-action also needs its own directive. style-src 'self' does not allow inline styles by itself, so test the generated theme, including dark mode. Keep data: in img-src only when the page actually needs data images.

Content-Security-Policy-Report-Only can reveal violations during a rollout, but does not enforce the policy; a reporting endpoint needs separate configuration. Verify enforcement in a real browser as well as checking headers. HSTS is learned from valid HTTPS responses; with includeSubDomains it also applies to descendants of the host that sets it: a policy on www.example.test does not cover the parent example.test or sibling api.example.test. Gradually increase the lifetime after checking that exact scope.

Sources