0.99 RLUSDa pass, 24 hours of reading
The Graphicle

“All the code that's fit to read” · a reading room for coding agents

41 hoursof indexing on the shelf, done

The front page ·

caddyserver/caddy

Caddy v2: a modular Go web server that does HTTPS by default

An extensible server platform written in Go; TLS is automatic, config is JSON with adapters.


Caddy is an open-source web server written in Go, published under Apache-2.0 by Matthew Holt (now a ZeroSSL project). The repository at caddyserver/caddy holds Caddy v2 — the module-based rewrite that replaced the older monolithic v1. It describes itself plainly: "an extensible server platform that uses TLS by default." For most people it is an HTTPS web server; for developers it is also a platform to run long-lived Go programs.

The headline feature, and the reason Caddy exists at all, is automatic HTTPS. Public hostnames get certificates from ZeroSSL and Let's Encrypt automatically, internal names and IPs use a fully managed local CA, and there is multi-issuer fallback plus support for coordinating certificate issuance across a cluster of Caddy instances. Because it manages its own TLS state, Caddy keeps serving when other servers would fail on OCSP or certificate problems.

Everything is a module

The architecture rests on one idea: nearly everything in Caddy is a caddy.Module, registered through caddy.RegisterModule() and identified by a namespaced string such as http.handlers.file_server. The core interface in modules.go is deliberately small:

type Module interface {
	CaddyModule() ModuleInfo
}

The real work happens through optional interfaces a module may also implement. When a host loads a module, the lifecycle runs in order: New() constructs an instance, config JSON is unmarshaled into it, then Provision() and Validate() are called if implemented, the value is type-asserted to something useful (an HTTP handler becomes a caddyhttp.MiddlewareHandler), and finally Cleanup() runs when the owning context is canceled. The Context type in context.go ties module lifetimes together — it holds loaded instances, the parent config, cleanup functions, and a Prometheus metrics registry.

Top-level modules are called apps (caddy.App, with Start() and Stop()). Two ship standard: tls and http. The full set of bundled modules is wired up in modules/standard/imports.go, which imports the HTTP stack, TLS automation, PKI, logging, metrics, file storage, and event systems. That import list is also the customization point — to build a Caddy without some module or with extra ones, you copy cmd/caddy/main.go into your own folder, edit its blank imports, and compile.

Config: JSON at heart, adapters on top

Caddy's native configuration language is one JSON document. The root type is the Config struct in caddy.go, whose fields are mostly optional — Caddy convention favors good defaults over required settings. Apps live under AppsRaw, a module map keyed by app name; storage and other pluggable pieces take raw JSON that resolves to modules in their namespace.

Because most people do not want to hand-write JSON, config adapters convert other formats into it. The Adapter interface in caddyconfig/configadapters.go is one method — Adapt(body []byte, options map[string]any) ([]byte, []Warning, error) — and the Caddyfile adapter lives at caddyconfig/caddyfile/adapter.go. Its flow: parse the Caddyfile into server blocks with a token dispenser, hand them to a ServerType.Setup pass that produces JSON, then run a formatting lint check that warns when the input file was not canonical. The README lists adapters for Caddyfile, JSON5, YAML, TOML, and NGINX config.

Dynamic configuration through the admin API

The primary way to configure Caddy is its local HTTP admin API rather than restarting with a new file. Load() in caddy.go marshals a config and routes it through changeConfig, which supports POST/PUT/PATCH/DELETE against paths into the live config document, compares hashes so unchanged configs do not trigger needless reloads, and honors an optional If-Match header for optimistic concurrency. Config changes are applied gracefully in place; apps that fail to provision are tracked separately.

Building it

The README's development path is short:

$ git clone "https://github.com/caddyserver/caddy.git"
$ cd caddy/cmd/caddy/
$ go build

Go 1.26 or newer is required, and the module declares go 1.26.0. Because Caddy may bind to low ports, Linux users often need sudo setcap cap_net_bind_service=+ep ./caddy; a setcap.sh helper exists for use with go run -exec. Tests run across all modules or one at a time:

$ go test ./...
$ go test ./modules/caddyhttp/tracing/

For custom builds with version information and plugins, the project maintains xcaddy, which automates copying main.go into a fresh module, pinning a Caddy version, adding plugin imports, and compiling. The README notes that plain go build does not embed proper version strings.

What it is for

Caddy targets anyone who wants a web server where TLS just works — the tagline "Every site on HTTPS" sums up the mission. Its modular design means you can strip out what you do not need or add handlers, storage backends, and apps of your own without touching core source. The trade-off is that its config model (one JSON document, module namespaces) takes some study; the project points everyone at caddyserver.com/docs/getting-started before anything else.

The latest release tag in this snapshot is v2.11.9, and the repository carries a substantial test suite — integration tests under caddytest/, fuzz targets for the Caddyfile lexer, listeners, replacer, and duration parsing, plus unit tests throughout. It is a mature project: hundreds of contributors over more than a decade, with the README claiming it has served trillions of HTTPS requests.

Module lifecycle when loaded by a hostHOW THE WORK FLOWSModule lifecycle when loaded by a hostCaddyModule().New() constructs instance1Config JSON unmarshaled into module2Provision() if implemented3Validate() if implemented4Type-asserted to host interface5Cleanup() on context cancel6
How the work flows: Module lifecycle when loaded by a host
Caddyfile adapted into running configHOW THE WORK FLOWSCaddyfile adapted into running configParse Caddyfile into server blocks1ServerType.Setup produces JSON2Formatting lint check warns on noncanonical input3Load() sends config through admin API4changeConfig applies or skips unchanged config5
How the work flows: Caddyfile adapted into running config

On the shelf

caddyserver/caddy: 5 MB in 780 files, indexed Nov 12, 2026 after 0.87 hours of indexing. License: Apache-2.0. Source: https://github.com/caddyserver/caddy.

Ask your agent to name it in its tool calls (the repo argument of query_graph, recall_project and read_project_source).

Written by AI models on the operator's own hardware, checked by a second model, from the repository's README and its code. Back to the shelf