This episode dissects static file serving: the file_server and root directives, directory browsing, index files, precompressed files, and try_files for SPA routing. You'll also learn dynamic roots with variables and common file server use cases.

Serving static files is the most basic job of a web server — and Caddy does it exceptionally well. In episode 5, you'll learn the file_server and root directives in depth, plus advanced features like directory browsing, precompressed files, and try_files for Single Page Applications.
Static files are the foundation of many sites: HTML, CSS, JavaScript, images, and downloadable documents. Even if you focus on reverse proxying, this episode still matters — almost every site needs at least a favicon, robots.txt, or static assets.
After this episode, you'll be able to build production-ready static sites, documentation, or portfolios with a short Caddyfile.
The file_server directive serves files from the document root. Simplest form:
example.com {
root * /var/www/mysite
file_server
}The default index files are index.html (followed by index.txt, and others). So a request to / returns /var/www/mysite/index.html if it exists.
If you want visitors to be able to browse directories:
files.example.com {
root * /srv/downloads
file_server browse
}With the browse keyword, Caddy shows a file listing when no index file exists. To control the index filenames:
example.com {
root * /var/www
file_server {
index index.html home.html
}
}The file_server block above declares: look for index.html first, then home.html as a fallback. This is useful for non-standard entry pages.
By default, Caddy refuses to serve files that start with a dot, like .env or .git. This is a wise security decision. Don't disable it unless you fully understand the risk.
Caddy can serve pre-compressed files (.gz, .br, .zst) if they sit next to the original file:
example.com {
encode zstd gzip
root * /var/www
file_server {
precompressed br gzip
}
}If /var/www/style.css.br exists, Caddy automatically sends it to browsers that support Brotli — reducing CPU load because compression isn't recomputed per request.
Caddy supports range requests (great for resuming video downloads) and ETags (cache validation) by default. No extra configuration needed. Just make sure file_server is active.
Info
For large files like videos, range requests let clients download part of a file and resume from the break point. Caddy handles this automatically as long as file_server is active.
try_files checks for file existence and picks the first one that exists:
example.com {
root * /var/www
try_files {path} {path}.html /index.html
file_server
}Meaning: try the exact path file, then the version with a .html extension, then fall back to /index.html. try_files {path} /index.html is the most common pattern for SPA routing.
React, Vue, and Angular applications need every route to fall back to index.html so the JavaScript router handles it:
spa.example.com {
root * /app/dist
try_files {path} /index.html
file_server
}With this pattern, a request for /dashboard, whose file doesn't exist, returns index.html, and the SPA router displays the right page. This is the pattern used in nearly every modern frontend deployment.
root determines the directory where files are looked up:
example.com {
root * /var/www/public
root /api /var/www/api-docs
file_server
}The first line, root * /var/www/public, applies to all paths; the second line overrides the root specifically for the /api path. So API docs stored in a separate directory can be served without moving files.
The document root can be dynamic using request placeholders:
*.example.com {
root * /var/www/sites/{host}
file_server
}Combining root * /var/www/sites/{host} with the {host} placeholder serves each subdomain from a different directory — an efficient pattern for static multi-tenancy. We'll go deeper into variables in episode 12.
root and file_server.try_files {path} /index.html.file_server browse.Episode 5 made you proficient in static file serving: file_server with the browse, index, and precompressed options; try_files for SPA routing and fallbacks; and root with dynamic variables for per-location or per-host configuration.
Key takeaways:
file_server + root is the core combination for static files.browse keyword shows a directory listing.try_files {path} /index.html is the standard SPA routing pattern.root can be set per location and is dynamic.In the next episode, episode 6, we move into reverse proxy basics — the reverse_proxy directive, header manipulation with header_up and header_down, proxying to localhost and containers, WebSocket and gRPC proxying, and transport options like h2c and timeouts. This is the gateway into the world of dynamic applications.