caddyserver/caddy
Architecture
Caddy is built around three ideas:
- A single JSON configuration document drives everything (
Configincaddy.go). - Almost every behavior is a module registered through
caddy.RegisterModule()(modules.go). - Top-level modules called apps implement
Start()/Stop()and run for the lifetime of the config.
This page sketches how those pieces fit together. For deeper dives, see Module system, Apps, and Admin API.
Layers
graph TD
CLI["cmd/caddy<br/>(spf13/cobra)"] -->|Start, Run, Reload| Core["caddy package<br/>(caddy.go, admin.go)"]
AdminAPI["Admin HTTP API<br/>localhost:2019"] -->|POST /load| Core
ConfigAdapters["Config adapters<br/>(Caddyfile, JSON 5, …)"] -->|adapt to JSON| Core
Core -->|loads apps| HTTP["http app<br/>modules/caddyhttp"]
Core -->|loads apps| TLS["tls app<br/>modules/caddytls"]
Core -->|loads apps| PKI["pki app<br/>modules/caddypki"]
Core -->|loads apps| Events["events app<br/>modules/caddyevents"]
HTTP -->|hands off TLS| TLS
HTTP -->|local CA| PKI
HTTP -->|emit| Events
TLS -->|emit| Events
Core -->|persists certs, OCSP, locks| Storage["Storage<br/>(certmagic.Storage)"]
Core --> Logging["Logging<br/>(zap)"]The Config struct
caddy.Config (in caddy.go) is the shape of the JSON document Caddy loads:
type Config struct {
Admin *AdminConfig
Logging *Logging
StorageRaw json.RawMessage `caddy:"namespace=caddy.storage inline_key=module"`
AppsRaw ModuleMap `caddy:"namespace="`
// …unexported runtime state…
}Three fields drive almost everything:
Admin— the admin HTTP endpoint that exposes/load,/config,/adapt,/pki,/reverse_proxy/upstreams, etc. (seeadmin.go).StorageRaw— a storage module ID; defaults tocaddy.storage.file_system(modules/filestorage/).AppsRaw— aModuleMapkeyed by app module ID (http,tls,pki,events, …).
AppsRaw carries caddy:"namespace=" (empty namespace) which means each key is the module ID for a top-level app. That single struct tag is the seam between the typed config and the dynamic module system.
Module IDs and namespaces
Modules are identified by dot-separated lowercase IDs (modules.go):
<name>for an app:http,tls,pki,events.<namespace>.<category>.<name>for everything else:http.handlers.file_server,http.matchers.path,caddy.logging.encoders.json,tls.issuance.acme,caddy.storage.file_system,dns.providers.….
The host module decides which namespace to load from via caddy:"namespace=…" struct tags. The inline_key option tells Caddy where to read the module name when configs are inline objects rather than module maps.
Module lifecycle
Every loaded module follows the same lifecycle hooks (interfaces defined in caddy.go):
graph LR
A[ModuleInfo.New] --> B[json.Unmarshal]
B --> C{Provisioner?}
C -->|yes| D[Provision]
D --> E{Validator?}
C -->|no| E
E -->|yes| F[Validate]
F --> G[use module]
E -->|no| G
G --> H{CleanerUpper?}
H -->|yes| I[Cleanup on ctx cancel]
H -->|no| J[done]Provisioning typically takes a caddy.Context (context.go), through which the module can load child modules, get a scoped logger, look up sibling apps, and register cleanup hooks. The whole tree is provisioned before any app's Start() is called.
Loading and reloading
caddy.Load(cfgJSON, forceReload) is the entry point used by both the CLI (caddy run, caddy reload) and the admin API (POST /load). It:
- Computes a hash of the new config; if it matches the running one and
forceReloadis false, it skips. - Parses the JSON into
Config,Provisions the tree, then callsStart()on each app in priority order. - On any failure, runs
Cleanup()and rolls back to the previous running config (unsyncedDecodeAndRunincaddy.go). - On success, swaps the running config under a mutex and signals the OS service manager via
notify(notify/).
Reloads are graceful: existing connections from the previous server keep running until they finish or the configured shutdown timeout elapses.
HTTP request lifecycle
The HTTP app composes a small middleware chain. For each server in http.servers:
sequenceDiagram
participant Client
participant Listener as caddy listener<br/>(listeners.go)
participant Server as caddyhttp.Server<br/>(server.go)
participant Routes as RouteList<br/>(routes.go)
participant Handler as Handler module
participant Upstream
Client->>Listener: TCP/QUIC connection
Listener->>Server: ServeHTTP(w, r)
Server->>Server: enforce listener wrappers, decode hop-by-hop headers
Server->>Routes: ApplyMatchersAndCompile -> middleware chain
Routes->>Handler: ServeHTTP(w, r)
Handler->>Upstream: optional forward (reverse_proxy, fastcgi)
Upstream-->>Handler: response
Handler-->>Server: error or nil
Server-->>Client: response, log entryRoutes are a list of Route structs (modules/caddyhttp/routes.go) where each route has matchers and handlers. At provision time the route list is compiled into a middleware function. The compiled chain is what serves requests. Subroutes (http.handlers.subroute) recursively embed another route list.
TLS and automatic HTTPS
The tls app owns CertMagic's certificate cache and the issuers. The http app's auto-HTTPS step (modules/caddyhttp/autohttps.go) inspects host matchers in every server, decides which names need certificates, and pushes them into tls.automation.policies. The tls app then provisions managed certificates, drives ACME challenges, and serves them via TLS connection policies. See Automatic HTTPS.
Observability hooks
modules/metrics/registers a/metricsendpoint on the admin API for Prometheus.logging.goandmodules/logging/build a tree of zap loggers driven by JSON config; per-module loggers come fromctx.Logger()(context.go).modules/caddyhttp/tracing/wraps the handler chain in OpenTelemetry spans.modules/caddyevents/lets modules emit events that other modules can subscribe to (e.g. for cert renewal hooks).
Where to read next
| You want to… | Start here |
|---|---|
| Understand the module registry and namespaces | Module system |
| See how the admin endpoint dispatches requests | Admin API |
| Trace HTTP routing and matchers | HTTP app |
| Learn how certificates are issued | TLS app, Automatic HTTPS |
| Write a Caddyfile that becomes JSON | Caddyfile |
| Build a plugin or add a module | Patterns and conventions |
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.