Caddy can serve static files with directory browsing enabled, but it’s not just about convenience; it’s fundamentally about how Caddy interprets file system paths and renders directory listings as dynamic HTML.

Let’s see it in action. Imagine you have a directory structure like this:

./public/
├── index.html
├── images/
│   └── logo.png
└── documents/
    └── report.pdf

And your Caddyfile looks like this:

:8080 {
    root * /path/to/your/public
    file_server browse
}

When you navigate to http://localhost:8080/ in your browser, Caddy will first look for index.html in the public directory. If it finds it, it serves that. If index.html were missing, and you navigated to http://localhost:8080/, Caddy would instead render a directory listing of the public directory, showing images/ and documents/. Navigating to http://localhost:8080/images/ would show logo.png, and http://localhost:8080/documents/ would show report.pdf.

The file_server directive is Caddy’s workhorse for serving static files. When you add the browse subdirective, you’re telling Caddy to enable directory listing only if an index.html (or other configured index file) is not present in the requested directory. This is a crucial distinction: browse doesn’t replace serving index.html; it acts as a fallback. Caddy maintains a list of default index files (like index.html, index.htm, index.txt) that it automatically looks for. If any of these are found, Caddy serves that file and does not generate a directory listing.

The root directive specifies the base directory from which Caddy will serve files. When browse is active, Caddy constructs the HTML for the directory listing dynamically. It reads the file system entries within the requested directory, sorts them (typically alphabetically), and generates an HTML page with links to each file and subdirectory. This HTML is not pre-generated; it’s created on-the-fly with each request.

The exact levers you control are primarily the root path and the presence of index files. You can also configure which files are considered index files using the index subdirective within file_server. For example:

:8080 {
    root * /path/to/your/public
    file_server browse {
        index default.htm index.html
    }
}

This tells Caddy to first look for default.htm, then index.html, and only if neither is found, to render the directory listing.

What many people don’t realize is that Caddy’s directory browsing is highly customizable through templates. While the default rendering is functional, you can provide your own HTML template to control the appearance and behavior of the directory listings. This is done using the templates subdirective within file_server. You’d create an HTML file (e.g., my_listing.html) with specific placeholders that Caddy populates with directory contents. This allows for branding, custom sorting, or even embedding additional information.

The next concept you’ll likely encounter is how to secure these browsable directories, preventing unauthorized access to sensitive files.

Want structured learning?

Take the full Caddy course →