How to Set Security Headers in Caddy

A complete guide to configuring all seven essential HTTP security headers in Caddy v2 using the header directive in your Caddyfile.


Caddy makes adding security headers exceptionally simple through its header directive. Unlike Apache or Nginx, Caddy automatically obtains and renews HTTPS certificates, so HSTS can be enabled with confidence from the start. This guide covers all seven essential headers using the Caddyfile format.

Prerequisites

  • Caddy v2 installed and serving your site.
  • A domain with DNS pointing to your server (Caddy handles HTTPS automatically).
  • Access to your Caddyfile (typically at /etc/caddy/Caddyfile).

Complete Caddyfile Configuration

Command
example.com { # ── Security Headers ────────────────────────────────────────────────── header { # Force HTTPS for 1 year; include subdomains; opt into preload lists Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" # Restrict resource loading; adjust script-src for your CDN/analytics needs Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'" # Prevent clickjacking X-Frame-Options "DENY" # Prevent MIME-type sniffing X-Content-Type-Options "nosniff" # Send origin only on same-site; nothing on cross-site downgrades Referrer-Policy "strict-origin-when-cross-origin" # Block sensitive browser features from the page and embedded iframes Permissions-Policy "camera=(), microphone=(), geolocation=(), fullscreen=(self), payment=()" # Disable the defunct IE/Chrome XSS Auditor X-XSS-Protection "0" # Remove headers that reveal server technology -Server -X-Powered-By } # ── End Security Headers ────────────────────────────────────────────── root * /var/www/example.com file_server }
Caddy Handles HTTPS AutomaticallyCaddy obtains and renews TLS certificates from Let's Encrypt automatically when you specify a domain name in the Caddyfile. You do not need to configure certificates manually, which makes enabling HSTS safe and straightforward.

Header Directive Syntax

The header directive supports three operations:

SyntaxOperation
Header-Name "value"Set (add or overwrite) a header
-Header-NameDelete a header from the response
?Header-Name "value"Set a header only if it is not already set

Header-by-Header Explanation

Strict-Transport-Security (HSTS)

Because Caddy manages certificates automatically and always serves HTTPS, HSTS is safe to enable immediately. max-age=31536000 caches the HSTS policy for one year. Add includeSubDomains only after confirming all subdomains also serve HTTPS.

Content-Security-Policy (CSP)

The example policy uses a strict default-src 'self' baseline. You will almost certainly need to expand it for your application. Common additions:

Command
# Example: allow Google Fonts and a CDN Content-Security-Policy "default-src 'self'; script-src 'self' https://cdn.example.com; style-src 'self' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: https:; frame-ancestors 'none'"
Report-Only Mode in CaddyWhile tuning your CSP, use Content-Security-Policy-Report-Only as the header name instead of Content-Security-Policy. Violations appear in the browser console but resources are not blocked.

X-Frame-Options

DENY prevents your site from being embedded in any iframe. Use SAMEORIGIN if you have legitimate same-origin iframe usage.

X-Content-Type-Options

nosniff tells browsers not to guess the content type. Files are always treated according to their declared Content-Type, preventing MIME-confusion attacks.

Referrer-Policy

strict-origin-when-cross-origin is the browser default from Chrome 85+. Setting it explicitly ensures consistent behaviour across all browsers and documents your intent.

Permissions-Policy

Restricts browser API access. The example blocks all sensitive features. Expand to (self) for features your own page uses, or add specific third-party origins for embedded widgets that legitimately need API access.

Removing Server Headers

The -Server and -X-Powered-By lines remove headers that reveal the web server technology. Caddy normally sends a Server: Caddy header by default.

Applying Headers to a Specific Path Only

To apply headers only to certain routes, use a handle block or add a matcher:

Command
example.com { # Apply security headers to all routes header { X-Content-Type-Options "nosniff" X-Frame-Options "DENY" Referrer-Policy "strict-origin-when-cross-origin" X-XSS-Protection "0" Permissions-Policy "camera=(), microphone=(), geolocation=()" -Server } # Apply HSTS and CSP only to HTML responses @html { header Content-Type text/html* } header @html { Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" Content-Security-Policy "default-src 'self'; frame-ancestors 'none'" } root * /var/www/example.com file_server }

Reverse Proxy Configuration

If Caddy is acting as a reverse proxy in front of an application server, add headers to the proxy responses in the same way:

Command
example.com { header { Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self'; frame-ancestors 'none'" X-Frame-Options "DENY" X-Content-Type-Options "nosniff" Referrer-Policy "strict-origin-when-cross-origin" Permissions-Policy "camera=(), microphone=(), geolocation=()" X-XSS-Protection "0" -Server } reverse_proxy localhost:3000 }

Applying Changes

bash
# Validate the Caddyfile caddy validate --config /etc/caddy/Caddyfile # Reload Caddy to apply changes (zero downtime) sudo systemctl reload caddy # Or use the Caddy API curl -X POST "http://localhost:2019/load" -H "Content-Type: text/caddyfile" --data-binary @/etc/caddy/Caddyfile # Verify with curl curl -I https://example.com

Or use the ShowDNS Security Headers Scanner for a full graded report.

Frequently Asked Questions

Does Caddy add any security headers automatically?

Caddy does not add security headers automatically (apart from HTTPS redirection). You must configure them explicitly. However, Caddy's automatic HTTPS makes enabling HSTS safe from the start.

Can I define headers globally for all sites?

Yes. Use a snippet and import it into each site block to avoid repetition:

Command
(security_headers) { header { Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" X-Frame-Options "DENY" X-Content-Type-Options "nosniff" Referrer-Policy "strict-origin-when-cross-origin" Permissions-Policy "camera=(), microphone=(), geolocation=()" X-XSS-Protection "0" -Server } } example.com { import security_headers root * /var/www/example.com file_server } another.com { import security_headers reverse_proxy localhost:4000 }

Related Articles