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
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
}Header Directive Syntax
The header directive supports three operations:
| Syntax | Operation |
|---|---|
Header-Name "value" | Set (add or overwrite) a header |
-Header-Name | Delete 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:
# 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'"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:
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:
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
# 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.comOr 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:
(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
}