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 ModuleInfo with a unique dotted-namespace ID and a constructor function (New func() Module). This is the hook that RegisterModule() 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 calls Start() on each provisioned app during config activation and Stop() 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 to CleanerUpper, 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. Provisioner is called after JSON unmarshal and is where a module wires up sub-modules, opens connections, and initializes state. Validator is called after Provision() to check configuration invariants. CleanerUpper is called when the module’s owning Context is canceled (on config swap or shutdown) to release resources. All three are opt-in via interface assertion, so a trivial module implementing only Module and App is valid.
  • Implementations: Most non-trivial modules implement Provisioner. Fewer implement Validator. Modules that open goroutines or files implement CleanerUpper.
  • 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 Provisioner and Validator allows 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.Handler but takes an explicit next Handler argument and returns an error. 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 in modules/caddyhttp/.
  • Design quality: Superior to http.Handler for middleware composition. The explicit next argument and error return solve two long-standing friction points in stdlib-based middleware chains. The separation from Handler (which has no next) 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.matchers namespace (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 only bool) 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 --adapter is specified on the CLI or when Caddy detects a non-JSON config file. The []Warning return 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 Dispenser cursor. When the Caddyfile adapter encounters a directive registered to a module, it calls UnmarshalCaddyfile on 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 Dispenser cursor 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 ServerBlock values) 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, in modules/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 caddytls app. Different loader modules can supply certificates from files, PEM strings, Vault, HSMs, or any other source. The AutomateLoader is 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 AutomateLoader special-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 implements AdminRouter and mounts the returned AdminRoute patterns. 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 AdminRoute struct pairs a URL pattern with an AdminHandler, 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.listeners namespace 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. MiddlewareHandler has 1, App has 2, Module has 1, Provisioner has 1. The largest meaningful interface is ServerType with 1 (but complex) method. This is exemplary ISP compliance.
  • Embedding: RequestMatcher is kept as a deprecated embedded compatibility shim alongside RequestMatcherWithError. The Certificate struct embeds tls.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 by ListenerWrapper), net.PacketConn (wrapped by PacketConnWrapper), http.ResponseWriter, *http.Request — but Caddy does not implement http.Handler for its internal middleware chain; it uses its own Handler interface with an error return instead.

Key abstractions#

  1. 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.

  2. MiddlewareHandler — The HTTP request processing contract. The explicit next Handler argument and error return are the design choices that differentiate Caddy’s middleware model from net/http standard practice. Every HTTP feature (proxying, file serving, auth, compression) is expressed through this interface.

  3. 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.

  4. Provisioner / CleanerUpper — Together these define a symmetric resource lifecycle: allocate in Provision(), release in Cleanup(). They are the mechanism that makes atomic config reload possible — each config generation lives in its own Context, and cleanup is automatic when the context is canceled.

  5. 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 + Adapter and register a new Caddyfile grammar for a new kind of server (DNS, SMTP, etc.).
  • New HTTP handlers/matchers: Implement Module + MiddlewareHandler (or RequestMatcherWithError), call RegisterModule() in init(). No central registration file to modify.
  • New TLS certificate sources: Implement Module + CertificateLoader in the tls.certificates namespace. Caddy automatically discovers it during TLS provisioning.
  • New config formats: Implement Adapter, call RegisterAdapter().
  • New admin endpoints: Implement AdminRouter in an existing module — no changes to the core admin server.
  • New listener behaviors: Implement ListenerWrapper in the caddy.listeners namespace.

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.