./ahmedhashim

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-origin prevents cross-origin attacks by isolating browsing contexts.
  • Origin-Agent-Cluster: ?1 requests 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-referrer prevents data leakage through referrer headers.
  • Strict-Transport-Security enforces 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: off reduces tracking through DNS prefetching.

Content Protection

  • X-Content-Type-Options: nosniff prevents MIME-type confusion attacks.
  • X-Frame-Options: SAMEORIGIN blocks clickjacking attempts.
  • X-XSS-Protection: 0 disables 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.