Caddy can serve static files with incredible performance, but the real magic is how it handles compression and caching automatically, often without you even noticing.
Let’s see Caddy in action. Imagine you have a directory named public with some assets:
public/
index.html
styles.css
script.js
image.png
And your Caddyfile looks like this:
:8080 {
root * /path/to/your/public
file_server
}
When you run caddy run and navigate to http://localhost:8080, Caddy doesn’t just dump the index.html file. It checks the browser’s Accept-Encoding header. If the browser supports gzip or br (Brotli), Caddy will compress styles.css and script.js on the fly (or serve pre-compressed versions if they exist, like styles.css.gz). It also sets Cache-Control headers, telling the browser how long it can cache these files, so subsequent requests are much faster.
The core problem Caddy solves for static files is reducing latency and bandwidth usage. Traditional web servers often require manual configuration for compression (like mod_deflate in Apache) and detailed cache directives. Caddy abstracts this away.
Internally, when a request comes in for a static file, Caddy’s file_server directive does a few things:
- File Existence Check: It looks for the requested file within the configured
root. - Content Negotiation (Encoding): It inspects the
Accept-Encodingheader. Ifgziporbrare present, and Caddy has a pre-compressed version (.gzor.br) of the requested file, it serves that. If not, and the file is compressible (text-based like HTML, CSS, JS), Caddy compresses it on the fly usinggziporbrand adds the appropriateContent-Encodingheader. - Caching Headers: It sets
ETagandCache-Controlheaders.ETagis a unique identifier for a specific version of a file. If the browser makes a subsequent request with anIf-None-Matchheader containing theETag, Caddy can respond with a304 Not Modifiedif the file hasn’t changed, saving bandwidth.Cache-Controltells the browser how long it can keep a local copy. - MIME Type Detection: It automatically detects the
Content-Typebased on the file extension (e.g.,.cssistext/css,.jsisapplication/javascript).
The file_server directive is highly configurable. You can customize the root directory, enable directory listings (though usually not recommended for production), and even set specific precompressed file extensions. For example, if you’ve pre-compressed your assets using a build tool:
:8080 {
root * /path/to/your/public
file_server {
precompressed br, gzip
}
}
This tells Caddy to prioritize .br and .gz files if they exist for the requested asset. This is generally more efficient than on-the-fly compression as it avoids CPU usage during request serving.
Most people know Caddy handles HTTPS automatically, but they often overlook how deeply integrated its static file serving is with modern web performance best practices. Caddy’s default Cache-Control headers are quite generous, often setting max-age=3600 (1 hour) for files that don’t have a version identifier in their name, and much longer for hashed assets (if Caddy can detect them, which it often can if your build process uses standard naming conventions). This automatic aggressive caching, combined with on-the-fly or pre-compression, means a significant portion of your static assets will be served with minimal latency and bandwidth.
The next step in optimizing static assets involves understanding how Caddy interacts with more advanced caching strategies, particularly when dealing with long-lived cache headers and cache-busting techniques.