Caddy — Interfaces#
Interface catalog#
Module#
- Package:
github.com/caddyserver/caddy/v2 - File:
modules.go:54 - Methods:
CaddyModule() ModuleInfo - Purpose: The single foundational contract of Caddy’s plugin system. Every plugin — handler, matcher, logger, storage backend, TLS loader — must implement this one-method interface, returning a
ModuleInfowith a unique dotted-namespace ID and a constructor function (New func() Module). This is the hook thatRegisterModule()and the module registry use to create and dispatch instances. - Implementations: Every module in the
modules/tree — hundreds of types across caddyhttp, caddytls, caddypki, caddyevents, logging, etc. - Design quality: Excellent ISP compliance. One method, one responsibility: self-identification. All other lifecycle concerns are expressed in separate, opt-in interfaces (Provisioner, Validator, CleanerUpper).
App#
- Package:
github.com/caddyserver/caddy/v2 - File:
caddy.go:98 - Methods:
Start() error Stop() error - Purpose: Defines a top-level Caddy application: something that can be started and stopped as part of the server lifecycle. Top-level JSON config keys (like
"http","tls","pki") map to modules implementing this interface. The core callsStart()on each provisioned app during config activation andStop()on old apps during config swap. - Implementations:
caddyhttp.App,caddytls.TLS,caddypki.PKI,caddyevents.App,logging.Logs - Design quality: Minimal and well-focused. The two-method lifecycle (start/stop) is the minimum needed for a reversible activation model. Resource cleanup beyond
Stop()is delegated toCleanerUpper, keeping this interface narrow.
Provisioner / Validator / CleanerUpper#
- Package:
github.com/caddyserver/caddy/v2 - File:
modules.go:296–317 - Methods:
// Provisioner Provision(Context) error // Validator Validate() error // CleanerUpper Cleanup() error - Purpose: Three optional lifecycle hooks for module instances.
Provisioneris called after JSON unmarshal and is where a module wires up sub-modules, opens connections, and initializes state.Validatoris called afterProvision()to check configuration invariants.CleanerUpperis called when the module’s owningContextis canceled (on config swap or shutdown) to release resources. All three are opt-in via interface assertion, so a trivial module implementing onlyModuleandAppis valid. - Implementations: Most non-trivial modules implement
Provisioner. Fewer implementValidator. Modules that open goroutines or files implementCleanerUpper. - Design quality: The three-phase lifecycle (provision → validate → cleanup) mirrors constructor/destructor semantics common in dependency-injection containers, but expressed purely through Go interfaces without any framework. The separation of
ProvisionerandValidatorallows fast-fail config validation without side effects.
MiddlewareHandler#
- Package:
github.com/caddyserver/caddy/v2/modules/caddyhttp - File:
caddyhttp.go:90 - Methods:
ServeHTTP(http.ResponseWriter, *http.Request, Handler) error - Purpose: The core HTTP middleware contract. Like
http.Handlerbut takes an explicitnext Handlerargument and returns anerror. This signature makes middleware composition explicit: each handler receives the next handler in the chain as an argument rather than having it captured in a closure. Returning an error instead of always writing a response allows the error middleware chain to intercept and format HTTP errors centrally. - Implementations:
reverseproxy.Handler,fileserver.FileServer,rewrite.Rewrite,headers.Headers,encode.Encode,templates.Templates, and all other HTTP handlers inmodules/caddyhttp/. - Design quality: Superior to
http.Handlerfor middleware composition. The explicitnextargument and error return solve two long-standing friction points in stdlib-based middleware chains. The separation fromHandler(which has nonext) cleanly distinguishes terminal handlers (responders) from pass-through middleware.
RequestMatcherWithError#
- Package:
github.com/caddyserver/caddy/v2/modules/caddyhttp - File:
caddyhttp.go:55 - Methods:
MatchWithError(*http.Request) (bool, error) - Purpose: Determines whether an HTTP request matches a given criterion. Matcher modules in the
http.matchersnamespace (host, path, method, header, remote_ip, expression, etc.) implement this interface. A route is activated only when all matcher sets evaluate to true. The error return allows matchers to abort the entire request on unexpected conditions. - Implementations:
MatchHost,MatchPath,MatchMethod,MatchHeader,MatchRemoteIP,MatchExpression(CEL), and others. - Design quality: Clean single-concern interface. The boolean + error return is idiomatic Go. The deprecated predecessor
RequestMatcher(returning onlybool) is kept for backward compatibility, illustrating a careful interface evolution strategy.
Adapter (config adapter)#
- Package:
github.com/caddyserver/caddy/v2/caddyconfig - File:
configadapters.go:26 - Methods:
Adapt(body []byte, options map[string]any) ([]byte, []Warning, error) - Purpose: Translates a non-JSON config format into Caddy’s canonical JSON representation. Adapters are registered by name (e.g.
"caddyfile") and invoked when--adapteris specified on the CLI or when Caddy detects a non-JSON config file. The[]Warningreturn allows partial conversion results with diagnostic messages, enabling soft failures. - Implementations:
caddyfile.Adapter(built-in), plus third-party adapters for YAML, NGINX, TOML. - Design quality: The
([]byte, []Warning, error)triple return is a thoughtful API design — it distinguishes fatal errors from recoverable warnings, and outputs raw JSON bytes so adapters do not need to import the entire config type graph.
Unmarshaler (Caddyfile)#
- Package:
github.com/caddyserver/caddy/v2/caddyconfig/caddyfile - File:
adapter.go:106 - Methods:
UnmarshalCaddyfile(d *Dispenser) error - Purpose: Allows a module to parse its own configuration from the Caddyfile token stream via the
Dispensercursor. When the Caddyfile adapter encounters a directive registered to a module, it callsUnmarshalCaddyfileon a new instance of that module. This decentralises Caddyfile parsing: each module owns its own DSL fragment rather than having a monolithic parser. - Implementations: Nearly all HTTP handler and matcher modules that support Caddyfile configuration.
- Design quality: Elegant delegation of parsing responsibility. The
Dispensercursor abstraction provides token-level access without exposing the full AST, keeping the per-module parsing surface manageable.
ServerType (Caddyfile)#
- Package:
github.com/caddyserver/caddy/v2/caddyconfig/caddyfile - File:
adapter.go:111 - Methods:
Setup([]ServerBlock, map[string]any) (*caddy.Config, []caddyconfig.Warning, error) - Purpose: Transforms a parsed Caddyfile (a slice of
ServerBlockvalues) into a complete*caddy.Config. This interface is the top-level Caddyfile → JSON translation point. The HTTP server type (httpcaddyfile) is the only implementation in the main repo, but the interface allows third-party server types (e.g. a DNS server) to define their own Caddyfile grammar. - Implementations:
httpcaddyfile(HTTP server type, inmodules/caddyhttp/httpcaddyfile/) - Design quality: Appropriately large scope for a “root” adapter. The interface itself is minimal (one method), even though implementations are complex. Well-placed at the right level of abstraction.
CertificateLoader#
- Package:
github.com/caddyserver/caddy/v2/modules/caddytls - File:
tls.go:920 - Methods:
LoadCertificates() ([]Certificate, error) - Purpose: Provides TLS certificates to the
caddytlsapp. Different loader modules can supply certificates from files, PEM strings, Vault, HSMs, or any other source. TheAutomateLoaderis a special no-op implementation that triggers Caddy’s ACME automation instead of loading static certs. - Implementations:
FileLoader,PEMLoader,AutomateLoader,StorageLoader, plus third-party loaders for Vault, cloud KMS, etc. - Design quality: Classic strategy pattern. The single-method interface enables maximum flexibility in certificate sourcing. The
AutomateLoaderspecial-casing shows pragmatic design: a “null object” that signals the TLS app to use a different code path.
AdminRouter#
- Package:
github.com/caddyserver/caddy/v2 - File:
admin.go:767 - Methods:
Routes() []AdminRoute - Purpose: Allows modules to register their own routes on the admin API server. When the admin server starts, it calls
Routes()on every provisioned module that implementsAdminRouterand mounts the returnedAdminRoutepatterns. Enables the admin API to be extended without modifying core code. - Implementations:
caddytls.TLS(exposes/pki/…routes),caddypki.PKI(exposes CA management endpoints), the Prometheus metrics handler. - Design quality: Simple and effective extensibility hook. The
AdminRoutestruct pairs a URL pattern with anAdminHandler, keeping the interface focused.
ListenerWrapper#
- Package:
github.com/caddyserver/caddy/v2 - File:
listeners.go:705 - Methods:
WrapListener(net.Listener) net.Listener - Purpose: Allows modules in the
caddy.listenersnamespace to intercept and transform network connections before they reach the HTTP server. Used for features like PROXY protocol parsing, TLS inspection, connection rate limiting, and HAProxy protocol support. - Implementations:
tls.TLSListenerWrapper,proxyprotocol.Listener(third-party), and others. - Design quality: Follows the classic decorator pattern on
net.Listener. Single-method interface is perfectly sized for this use case.
Interface patterns#
- Size distribution: Very lean. The majority of Caddy’s interfaces have 1–2 methods.
MiddlewareHandlerhas 1,Apphas 2,Modulehas 1,Provisionerhas 1. The largest meaningful interface isServerTypewith 1 (but complex) method. This is exemplary ISP compliance. - Embedding:
RequestMatcheris kept as a deprecated embedded compatibility shim alongsideRequestMatcherWithError. TheCertificatestruct embedstls.Certificate(struct embedding, not interface). Interface embedding is minimal — Caddy prefers flat, small interfaces over composed ones. - Implicit satisfaction: Interfaces are defined by consumers (the core or caddyhttp/caddytls) and satisfied by providers (modules). This is idiomatic Go: the core defines the contract, modules satisfy it. Interface guards (e.g.
var _ caddy.App = (*App)(nil)) are used throughout to catch mismatches at compile time. - stdlib interfaces used:
net.Listener(wrapped byListenerWrapper),net.PacketConn(wrapped byPacketConnWrapper),http.ResponseWriter,*http.Request— but Caddy does not implementhttp.Handlerfor its internal middleware chain; it uses its ownHandlerinterface with an error return instead.
Key abstractions#
Module— The single most architecturally significant interface. It is the contract by which any piece of functionality is registered with and loaded by the Caddy core. Without it, the entire plugin system collapses. Its minimal surface (one method) makes third-party module development frictionless.MiddlewareHandler— The HTTP request processing contract. The explicitnext Handlerargument anderrorreturn are the design choices that differentiate Caddy’s middleware model fromnet/httpstandard practice. Every HTTP feature (proxying, file serving, auth, compression) is expressed through this interface.App— The lifecycle boundary for top-level features.Start()/Stop()separates resource acquisition from resource release, enabling the atomic config-swap guarantee: new apps start before old ones stop, ensuring zero-downtime reloads.Provisioner/CleanerUpper— Together these define a symmetric resource lifecycle: allocate inProvision(), release inCleanup(). They are the mechanism that makes atomic config reload possible — each config generation lives in its ownContext, and cleanup is automatic when the context is canceled.Adapter— The config portability interface. A single 3-return-value method makes Caddy’s canonical JSON format accessible from any human-readable config DSL. The existence of this interface is what allows Caddy to have both a user-friendly Caddyfile syntax and a machine-friendly JSON API without compromising either.
Interface-driven extensibility#
Caddy’s extensibility model is almost entirely interface-based, with no dynamic linking, no reflection-heavy frameworks, and no code generation:
- New server types: Implement
ServerType+Adapterand register a new Caddyfile grammar for a new kind of server (DNS, SMTP, etc.). - New HTTP handlers/matchers: Implement
Module+MiddlewareHandler(orRequestMatcherWithError), callRegisterModule()ininit(). No central registration file to modify. - New TLS certificate sources: Implement
Module+CertificateLoaderin thetls.certificatesnamespace. Caddy automatically discovers it during TLS provisioning. - New config formats: Implement
Adapter, callRegisterAdapter(). - New admin endpoints: Implement
AdminRouterin an existing module — no changes to the core admin server. - New listener behaviors: Implement
ListenerWrapperin thecaddy.listenersnamespace.
The pattern is uniform across all extension points: define a small interface, implement it on a struct that also implements Module, and register in init(). The namespace-scoped dispatch in ctx.LoadModule() does the rest. This is Caddy’s core architectural insight: the same mechanism (module registry + interface assertion) works for every extension point in the system.