A Sensible Caddyfile
After over a decade of wrestling with nginx modules and Apache configurations, switching to Caddy feels like a breath of fresh air.
Written in Go, it’s incredibly fast & batteries included to start serving requests. I’ve added some additional security headers & sensible defaults for an opinionated Caddyfile to use in a modern web stack:
example.com {
@static path *.css *.js *.ico *.txt /sitemap.xml
handle @static {
file_server {
root /opt/app/public
}
}
reverse_proxy localhost:8080
encode {
zstd
gzip
}
header {
-Via
Content-Security-Policy "default-src 'self'; script-src 'self'; connect-src 'self'; img-src 'self'; style-src 'self'; base-uri 'self'; form-action 'self';"
Cross-Origin-Opener-Policy "same-origin"
Origin-Agent-Cluster "?1"
Permissions-Policy "accelerometer=(), ambient-light-sensor=(), attribution-reporting=(), autoplay=(), bluetooth=(), browsing-topics=(), camera=(), compute-pressure=(), display-capture=(), document-domain=(), encrypted-media=(), fullscreen=(), gamepad=(), geolocation=(), gyroscope=(), hid=(), identity-credentials-get=(), idle-detection=(), local-fonts=(), magnetometer=(), microphone=(), midi=(), otp-credentials=(), payment=(), picture-in-picture=(), publickey-credentials-create=(), publickey-credentials-get=(), screen-wake-lock=(), serial=(), speaker-selection=(), storage-access=(), usb=(), web-share=(), window-management=(), xr-spatial-tracking=()"
Referrer-Policy "no-referrer"
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
X-Content-Type-Options "nosniff"
X-DNS-Prefetch-Control "off"
X-Download-Options "noopen"
X-Frame-Options "SAMEORIGIN"
X-Permitted-Cross-Domain-Policies "none"
X-XSS-Protection "0"
}
}
www.example.com {
redir https://example.com{uri} permanent
}
This configuration follows the principle of deny everything by default, enable only what’s proven necessary. Rather than hoping malicious scripts won’t abuse browser features, we make it impossible for them to access anything sensitive. It acts like an allowlist: if it’s not explicitly permitted, it’s blocked.
Automatic HTTPS & Static Assets
Caddy automatically provisions and renews Let’s Encrypt TLS certificates via the ACME protocol. This means no more manual certificate management like with other webservers. Simply specifying a domain name enables HTTPS by default.
Static files are served directly from disk for better performance, while dynamic requests are proxied to the application server. Modern compression (Zstandard + gzip fallback) reduces bandwidth usage.
Content Security Policy
The strict CSP allows resources only from the same origin ('self'),
blocking inline scripts and styles, external resources, and potential XSS
vectors.
This basic policy can be extended with nonces for inline content or additional domains as needed. However, doing so will require moving the CSP header to application layer to set nonces on each request.
Process Isolation
Cross-Origin-Opener-Policy: same-originprevents cross-origin attacks by isolating browsing contexts.Origin-Agent-Cluster: ?1requests that your site gets its own isolated process, preventing resource-intensive pages from slowing down other sites and improving security against side-channel attacks.
Permissions
The extremely restrictive permissions policy blocks all browser APIs by
default. Every feature is explicitly set to () (deny all), creating a
locked-down environment where no JavaScript can access device sensors, media
devices, or advanced browser features.
This might seem overly cautious, but it follows the security principle of
least privilege.
As we develop the application and identify specific features
we need (like fullscreen for image galleries or web-share for social features),
we’ll selectively enable them by changing the relevant policies from () to
self.
Privacy & Transport Security
Referrer-Policy: no-referrerprevents data leakage through referrer headers.Strict-Transport-Securityenforces HTTPS with preload support in browsers, however you are required to keep this enabled for 1 year when applying for HSTS preload.X-DNS-Prefetch-Control: offreduces tracking through DNS prefetching.
Content Protection
X-Content-Type-Options: nosniffprevents MIME-type confusion attacks.X-Frame-Options: SAMEORIGINblocks clickjacking attempts.X-XSS-Protection: 0disables legacy XSS filters (CSP is better, but a layered approach to security is encouraged).
For most web applications, this provides maximum security from day one. As your application grows and requires specific browser features, you can methodically enable them one at a time. This ensures you understand exactly what capabilities you’re granting and why.