Caddy is a web server that ships as a single 53159936-byte executable. Point it at a directory, give it ten lines of configuration, and it serves that directory over HTTP. The same binary is also a reverse proxy, and it will fetch and renew TLS certificates on its own.

The problem it removes is the ritual around small web tasks. Standing up nginx for a file share means editing a conf file, adding a site, reloading, then reading the error log when nothing appears. Caddy reads one file and starts.

Everything below comes from one session on Windows 10 22H2: unpack, validate, serve, hammer the endpoint, then look at what the process left on disk.

One binary, one config file

The release zip is 18479498 bytes and contains three files. There is no MSI, no registry write, and no runtime to install. Unpacking took 0.33 seconds.

FileBytesRole
caddy.exe53159936The server and CLI
README.md1725Pointer to the docs site
LICENSE1099Apache 2.0
Whole zip184794983 files after unpacking

Apache 2.0 matters if you plan to bundle it. Redistribution inside a commercial product is allowed, and the notice file is the only obligation.

The version call costs 0.39 seconds

caddy version returned in 0.39 seconds and printed a single line with the tag and the build hash. That is the slowest of the three commands measured here, because Windows scans a fresh 53 MB executable on first run.

Validate the config before you start

caddy adapt translates the Caddyfile into the JSON that the server actually consumes. It runs without binding a port, which makes it the cheapest way to catch a typo.

caddy.exe adapt --config Caddyfile --pretty
# 10 lines of Caddyfile
# -> 492 bytes / 34 lines of JSON in 0.24 seconds

The Caddyfile used in this test sets two global options and one site block. Port 8082 replaces the default 80 so the test does not collide with anything already listening, and the admin endpoint is pinned to loopback.

DirectiveValue hereWhy
admin127.0.0.1:2019Config API, kept off the network
http_port8082Avoids the privileged port
root *the shared folderDocument root
file_serverbrowseDirectory listing on

Start it and measure the endpoint

The service accepted connections 0.61 seconds after launch. The first directory listing painted in 0.05 seconds, and a 5 MB file came down in 0.01 seconds over loopback.

MeasurementResult
Startup to first accepted request0.61 seconds
Directory page load0.05 seconds
Fastest of 50 requests0.8 ms
Median of 50 requests0.9 ms
Slowest of 50 requests12.4 ms
5 MB file download0.01 seconds
Admin API round trip1.2 ms

The slow outlier is the filesystem, not the server

Of fifty sequential requests, forty-nine landed between 0.8 and 1.1 milliseconds. The single 12.4 ms request is the cost of the directory read after Windows dropped the metadata cache.

That gap is worth knowing before you benchmark anything. A single slow sample says more about the disk than about the server, so report the median alongside the maximum.

The admin API answers in 1.2 ms

GET /config/ on port 2019 returns the running configuration as JSON. In this test it came back in 1.2 ms with two top-level keys, admin and apps. That endpoint is the reason the config can be changed without editing files by hand.

What it writes on first run

The program directory stays clean. Caddy puts its state under %APPDATA%\Caddy, and three files appear after the first successful start.

FileBytesContent
autosave.json268Snapshot of the last active config
instance.uuid36Instance identifier
last_clean.json110Timestamp of the last certificate store cleanup
%APPDATA%\Caddy\autosave.json     268 bytes
%APPDATA%\Caddy\instance.uuid      36 bytes
%APPDATA%\Caddy\last_clean.json   110 bytes

Backing up a working setup means copying that folder. Restoring it on another machine keeps the instance identity, which matters once certificates are issued against a domain.

Five things that bite

Port 80 is taken and the process just exits

There is no retry and no interactive prompt. The log prints a bind failure and the process ends. Set http_port and https_port in the global block before starting, which is what the 8082 in this test is for.

The directory returns 404 with the files right there

file_server without the browse argument serves index files only. Ask for a folder that has no index.html and you get 404 instead of a listing.

Editing the Caddyfile does nothing

The file is read once at startup. Reload through caddy reload --config Caddyfile or restart the process. The admin API can also push a new config directly.

A certificate request for localhost never resolves

Automatic HTTPS tries to validate against a public resolver. On a loopback-only host that validation cannot succeed, so the site stays on HTTP or the request fails after retries. Keep the internal CA for local testing.

The admin endpoint listens on every interface

The default value has no host part, which means all interfaces. On a machine with a public address that exposes config writing to anyone who can reach port 2019. Pin it to 127.0.0.1, as this test does.

When it is worth it

For a shared folder on a small network, the whole setup is one binary and ten lines. Startup at 0.61 seconds and a 0.9 ms median make it feel like nothing is in the path.

As a reverse proxy in front of a few services it also holds up, and certificate renewal is the part most people stop maintaining by hand.

It is not the right tool if you need modules that only exist for nginx, or if the team already runs an nginx configuration management setup. Porting working configs costs more than it saves.

FigureValue
Executable53159936 bytes
Zip18479498 bytes
Config translation0.24 seconds
Startup0.61 seconds
Median request0.9 ms