How Caddy Config Loading and Validation Works: From Caddyfile to Runtime
Caddy loads configuration through the admin API or CLI, adapts non-JSON formats to native JSON, provisions modules via unsyncedDecodeAndRun, and validates each module's Validate() method before atomically applying changes with automatic rollback on failure.
Caddy's configuration system is designed for zero-downtime reloads and strict validation before activation. Whether you're using the Caddyfile format, JSON, or the admin API, the server follows a consistent pipeline to ensure only valid configurations reach runtime. This article examines the source code in caddyserver/caddy to explain exactly how the config loading and validation process operates.
Config Loading Pipeline
Admin API Endpoints (/load and /adapt)
The admin module registers configuration endpoints in caddyconfig/load.go. The adminLoad.Routes function sets up /load and /adapt handlers that accept configuration via HTTP【adminLoad.Routes】.
The /load endpoint (handleLoad) performs the following:
- Reads the request body containing raw configuration
- If the
Content-Typeis not JSON, callsadaptByContentTypeto execute the appropriate config adapter (e.g., converting Caddyfile to JSON)【adaptByContentType】 - Invokes
caddy.Load(body, forceReload)whereforceReloadis set totruewhen the request includesCache-Control: must-revalidate【handleLoad】
The /adapt endpoint simply returns the JSON result of the adaptation without applying it, useful for dry-run validation of Caddyfile syntax.
CLI Configuration Loading
The command-line entry point in cmd/main.go provides the LoadConfig helper function that reads configuration files and selects adapters based on file extensions【LoadConfig】. When you run caddy run or caddy test, the CLI calls caddy.Load with the raw bytes from the file.
Core Load Function and changeConfig
The caddy.Load function in caddy.go serves as the central entry point for both the admin API and CLI【caddy.Load】. It notifies the OS that a reload is occurring and delegates to changeConfig(http.MethodPost, "/"+rawConfigKey, cfgJSON, "", forceReload).
The changeConfig function in caddy.go acts as the atomic mutation hub for all configuration changes【changeConfig】. It performs these critical steps:
- If-Match handling – Validates optional preconditions using ETag headers to prevent stale updates
- Mutation – Calls
unsyncedConfigAccessto update the in-memory raw config map - JSON encoding – Serializes the entire configuration structure as
newCfg - Short-circuit optimization – Compares the new JSON against
rawCfgJSON; if identical andforceReloadis false, returnserrSameConfigto skip unnecessary reloads - ID indexing – Walks the config tree to build an ID-to-path index via
indexConfigObjectsfor efficient module lookups - Decode and provision – Invokes
unsyncedDecodeAndRun(newCfg, true)to instantiate modules and run validation - Rollback protection – If provisioning fails, unmarshals the previous raw config back into
rawCfgto maintain server stability - Persistence – Stores the new JSON in
rawCfgJSONfor future comparison
Provisioning and Validation
Module Provisioning
Inside caddy.go, the unsyncedDecodeAndRun function handles the transition from static JSON to live modules. It decodes the JSON into a Config struct and calls Provision on each module (apps, handlers, TLS managers, etc.). This phase instantiates objects and wires up dependencies before any traffic flows through them.
The Validator Interface
After provisioning, Caddy enforces semantic validation through the Validator interface defined in modules.go【Validator interface】. Any module requiring additional validation implements the Validate() method:
- TLS application –
modules/caddytls/tls.gocontainsTLS.Validateto check certificate configurations【TLS.Validate】 - HTTP handlers – Various handlers in
modules/caddyhttp/handlers.goimplementValidateto verify routing logic - Logging filters –
modules/logging/filters.goincludesMultiRegexpFilter.Validateto check regular expression syntax【MultiRegexpFilter.Validate】
If any Validate() method returns an error, the error bubbles up through unsyncedDecodeAndRun, triggering the rollback mechanism in changeConfig and causing the admin API to return HTTP 400.
CLI Validation Command
The caddy validate sub-command in cmd/commandfuncs.go provides explicit validation without starting the server【cmdValidateConfig】. The handler loads the configuration file, adapts it if necessary, and calls caddy.Validate(cfg). This function is a thin wrapper around run(cfg, false) that provisions and validates all modules without starting listeners, then disposes of resources【Validate】.
Practical Configuration Examples
Loading via Admin API
Send a Caddyfile directly to the running server for hot-reloading:
curl -X POST http://localhost:2019/load \
-H "Content-Type: application/caddyfile" \
-d '
:80 {
respond "Hello, Caddy!"
}
'
The endpoint automatically adapts the Caddyfile to JSON, runs provisioning and validation, and applies the configuration atomically.
Programmatic Loading in Go
Embed configuration reloading in your Go application:
import (
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig"
)
func reloadFromFile(path string) error {
// Read file and detect adapter by extension
raw, adapter, src, err := caddyconfig.LoadConfig(path)
if err != nil {
return err
}
// Adapt non-JSON formats
if adapter != "" {
raw, _, err = caddyconfig.GetAdapter(adapter).Adapt(raw, nil)
if err != nil {
return err
}
}
// Load, validate, and provision (force reload)
return caddy.Load(raw, true)
}
CLI Validation
Validate a configuration file before deployment:
caddy validate --config ./Caddyfile
This parses the file, adapts it to JSON, provisions all modules, and runs validation without starting the server or binding to ports.
Summary
- Caddy loads configuration through the admin API (
/load,/adapt) or CLI (caddy run,caddy validate), with adapters converting Caddyfile and other formats to native JSON incaddyconfig/load.goandcmd/main.go - The
changeConfigfunction incaddy.goorchestrates atomic configuration updates with If-Match preconditions, short-circuit optimization for identical configs, and automatic rollback on failure - Module provisioning occurs in
unsyncedDecodeAndRun, which instantiates apps and handlers before they accept traffic - Validation happens via the
Validatorinterface inmodules.go, where modules like TLS and HTTP handlers implementValidate()to catch misconfigurations before activation - Rollback protection ensures that if provisioning or validation fails, the previous configuration remains active in
rawCfg, maintaining server stability
Frequently Asked Questions
What happens if a Caddy configuration fails validation?
If any module's Validate() method returns an error during the unsyncedDecodeAndRun phase, changeConfig catches the error and rolls back to the previous configuration. The function unmarshals the prior raw JSON back into rawCfg, ensuring the running server remains unaffected, and returns HTTP 400 for API requests or a non-zero exit code for CLI commands.
How does Caddy handle configuration reloads without downtime?
Caddy uses an atomic two-phase commit process in changeConfig. It first provisions and validates the new configuration alongside the running one. Only after all modules pass validation does it switch the active configuration. If the new JSON matches the current rawCfgJSON and forceReload is false, it returns errSameConfig to avoid unnecessary reloads entirely.
Can I validate a Caddyfile before applying it?
Yes. Use the caddy validate --config ./Caddyfile command implemented in cmd/commandfuncs.go. This runs the full provisioning and validation pipeline—including adapter conversion and all module Validate() calls—without starting the server or binding to network ports.
What is the difference between /load and /adapt admin endpoints?
The /load endpoint in caddyconfig/load.go adapts configuration (if necessary) and immediately applies it to the running server via caddy.Load. The /adapt endpoint only performs the conversion from Caddyfile or other formats to JSON and returns the result without modifying the running configuration, making it useful for debugging or previewing changes.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →