This episode covers performance: the encode directive with gzip, brotli, and zstd, precompressed files, Cache-Control for static assets, HTTP/2 and HTTP/3, and tuning keep-alive and timeouts.

Slow sites lose visitors. Fortunately, most latency can be reduced at the web server level: compressing responses, leveraging the browser cache, and using modern protocols. Episode 21 covers performance optimization in Caddy.
You'll learn the encode directive for gzip, brotli, and zstd compression, precompressed files to save CPU, Cache-Control settings for static assets, HTTP/2 and HTTP/3 that activate automatically, and connection tuning for production loads.
All of these techniques make a site feel instant without changing the application at all.
encode compresses responses before sending them:
example.com {
encode zstd gzip
root * /var/www
file_server
}Caddy picks the best algorithm the browser supports: zstd (most efficient) with a gzip fallback. The order in the Caddyfile determines the priority.
A few things to note:
For sites with large assets, per-request compression consumes CPU. The solution: compress once at build time, serve forever:
example.com {
encode zstd gzip
root * /var/www
file_server {
precompressed zstd gzip
}
}If style.css.zst and style.css.gz sit next to style.css, Caddy sends the compressed version without extra work. The encode + precompressed combination gives flexibility: files compressed at build time are used, the rest are compressed on the fly.
Create compressed versions at build time:
gzip -k -9 style.css
brotli -k -9 style.cssgzip -k -9 style.css produces style.css.gz with maximum compression. Add this step to your frontend build pipeline.
Browsers store assets that are given cache permission. Configure with a header:
example.com {
root * /var/www
header {
Cache-Control "public, max-age=86400"
}
file_server
}Cache-Control public, max-age=86400 makes browsers store the response for a day. For assets that never change:
example.com {
@aset {
path /static/*
}
header @aset Cache-Control "public, max-age=31536000, immutable"
root * /var/www
file_server
}immutable tells the browser that assets with hashed names won't change — they can be stored for a year. header @aset ... applies it only to the /static/* paths.
Because hashed assets can be cached for a long time, make sure the filename changes when content changes. Modern build tools (webpack, vite) add a hash to filenames automatically — style.a1b2c3.css. This is cache busting: new content, new name, browsers fetch the new version.
Caddy enables HTTP/2 and HTTP/3 automatically:
No configuration is needed — you just need to make sure UDP port 443 isn't blocked by the firewall.
The main thing you should do: let Caddy choose the protocol and don't force a downgrade.
For production loads, adjust connection limits:
{
servers {
protocols h1 h2 h3
max_header_size 64KB
timeouts {
idle 5m
}
}
}
example.com {
encode zstd gzip
root * /var/www
file_server
}protocols h1 h2 h3 — enable all protocols including QUIC.max_header_size 64KB — cap the header size.idle 5m — idle connections survive 5 minutes before closing.Keep-alive keeps connections alive between requests, avoiding repeated handshake costs. Adjust idle to your traffic pattern — too short means connections are frequently recreated, too long holds resources.
Caddy handles buffering and connection pooling internally. For reverse proxies, we already saw dial_timeout and max_conns_per_host in episode 14. The principle: leave the defaults alone, change them only when there's a measurable problem.
Episode 21 optimized performance: encode for gzip, brotli, and zstd compression, precompressed files to save CPU, Cache-Control for static assets with immutable and cache busting, automatic HTTP/2 and HTTP/3, and keep-alive and timeout tuning.
Key takeaways:
encode zstd gzip gives the best compression with a fallback.Cache-Control public, max-age=31536000, immutable for hashed assets.idle timeout affect connection costs.In the next episode, episode 22, we'll cover logging & monitoring — the log directive with Common, JSON, and custom formats, output to files or stdout, access log fields, aggregation with ELK and Loki, Prometheus metrics via the admin API, and debugging techniques with log levels.