This episode covers security at the header level: the header directive for adding, changing, and removing headers, security headers like CSP and HSTS, complete CORS configuration with preflight, and practices for hiding server information.

Many web attacks are prevented not in the application, but at the protocol level — through HTTP headers. Browsers read headers like Content-Security-Policy and X-Frame-Options to make security decisions before rendering a page. Episode 20 covers how Caddy controls these headers.
You'll learn the header directive to add, change, and remove headers, the list of security headers you should install, complete CORS configuration for cross-domain APIs, and how to hide server information from the public.
Headers are a cheap yet highly effective defense layer — and all of it can be done from the Caddyfile without touching your application.
header adds headers to every response:
example.com {
header {
X-Frame-Options DENY
X-Content-Type-Options nosniff
Referrer-Policy strict-origin-when-cross-origin
-Server
}
root * /var/www
file_server
}The rules: new header names are added; names prefixed with - are removed. -Server removes the Server header Caddy sends, hiding the technology behind the site.
Headers can be restricted with a matcher:
example.com {
@api {
path /api/*
}
header @api Access-Control-Allow-Origin *
root * /var/www
file_server
}header @api Access-Control-Allow-Origin * adds the CORS header only to API paths.
Some headers are essential for modern sites:
example.com {
header {
X-Frame-Options DENY
X-Content-Type-Options nosniff
X-XSS-Protection 0
Permissions-Policy geolocation=(), camera=(), microphone=()
}
root * /var/www
file_server
}X-XSS-Protection 0 actually disables an old, problematic browser feature; modern XSS protection comes from CSP.
CSP is the strongest defense against XSS. This header tells browsers which content sources are allowed:
example.com {
header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self'"
root * /var/www
file_server
}default-src 'self' only allows content from your own domain. External scripts are blocked unless added to the list.
The HSTS header forces browsers to always use HTTPS:
example.com {
header Strict-Transport-Security "max-age=63072000; includeSubDomains"
root * /var/www
file_server
}header Strict-Transport-Security ... with a two-year max-age and includeSubDomains rejects HTTP connections for the entire domain. We already met it in episode 8.
CORS lets browsers call APIs from another domain. The browser sends an OPTIONS preflight request before the actual request, and Caddy must respond correctly:
api.example.com {
@preflight {
method OPTIONS
}
handle @preflight {
header Access-Control-Allow-Origin https://app.example.com
header Access-Control-Allow-Methods "GET, POST, PUT, DELETE"
header Access-Control-Allow-Headers "Content-Type, Authorization"
header Access-Control-Max-Age "86400"
respond 204
}
header Access-Control-Allow-Origin https://app.example.com
reverse_proxy localhost:8080
}The @preflight block handles OPTIONS requests and returns 204 without touching the backend. The Access-Control-Allow-Origin header on normal responses lets browsers read the response.
If the application uses cross-domain cookies:
api.example.com {
header Access-Control-Allow-Origin https://app.example.com
header Access-Control-Allow-Credentials true
reverse_proxy localhost:8080
}Access-Control-Allow-Credentials true allows cookies to be sent with cross-domain requests. Note: with credentials, Allow-Origin can't be an asterisk — it must be a specific domain.
After applying headers, test the results:
curl -I https://example.comcurl -I shows the response headers received. Make sure security headers appear and the Server header doesn't leak. Tools like securityheaders.com can give an automated assessment.
X-Frame-Options: DENYX-Content-Type-Options: nosniffContent-Security-PolicyReferrer-Policy: strict-origin-when-cross-originStrict-Transport-Security (HTTPS)Permissions-Policy that restricts browser APIsServer and backend headersEpisode 20 completed security at the header level: the header directive for adding, changing, and removing headers, security headers like CSP, HSTS, and X-Frame-Options, complete CORS configuration with OPTIONS preflight and credentials, and practices for hiding server information.
Key takeaways:
header adds headers; a - prefix removes them.-Server hides backend technology from the public.Allow-Credentials true requires a specific origin.curl -I.In the next episode, episode 21, we'll cover compression & caching — the encode directive with gzip, brotli, and zstd, precompressed files, Cache-Control headers for static assets, HTTP/2 and HTTP/3, and performance tuning like keep-alive and timeouts.