# How Caddy Config Loading and Validation Works: From Caddyfile to Runtime

> Understand Caddy config loading and validation. Discover how Caddy adapts formats, provisions modules, validates configurations, and applies changes with rollback.

- Repository: [Caddy/caddy](https://github.com/caddyserver/caddy)
- Tags: internals
- Published: 2026-03-03

---

**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`](https://github.com/caddyserver/caddy/blob/main/caddyconfig/load.go). The `adminLoad.Routes` function sets up **/load** and **/adapt** handlers that accept configuration via HTTP【[adminLoad.Routes](https://github.com/caddyserver/caddy/blob/master/caddyconfig/load.go#L54-L65)】.

The **`/load`** endpoint (`handleLoad`) performs the following:

1. Reads the request body containing raw configuration
2. If the `Content-Type` is not JSON, calls `adaptByContentType` to execute the appropriate config adapter (e.g., converting Caddyfile to JSON)【[adaptByContentType](https://github.com/caddyserver/caddy/blob/master/caddyconfig/load.go#L77-L115)】
3. Invokes `caddy.Load(body, forceReload)` where `forceReload` is set to `true` when the request includes `Cache-Control: must-revalidate`【[handleLoad](https://github.com/caddyserver/caddy/blob/master/caddyconfig/load.go#L73-L84)】

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`](https://github.com/caddyserver/caddy/blob/main/cmd/main.go) provides the `LoadConfig` helper function that reads configuration files and selects adapters based on file extensions【[LoadConfig](https://github.com/caddyserver/caddy/blob/master/cmd/main.go#L108-L115)】. 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`](https://github.com/caddyserver/caddy/blob/main/caddy.go) serves as the central entry point for both the admin API and CLI【[caddy.Load](https://github.com/caddyserver/caddy/blob/master/caddy.go#L12-L23)】. It notifies the OS that a reload is occurring and delegates to `changeConfig(http.MethodPost, "/"+rawConfigKey, cfgJSON, "", forceReload)`.

The `changeConfig` function in [`caddy.go`](https://github.com/caddyserver/caddy/blob/main/caddy.go) acts as the atomic mutation hub for all configuration changes【[changeConfig](https://github.com/caddyserver/caddy/blob/master/caddy.go#L59-L125)】. It performs these critical steps:

1. **If-Match handling** – Validates optional preconditions using ETag headers to prevent stale updates
2. **Mutation** – Calls `unsyncedConfigAccess` to update the in-memory raw config map
3. **JSON encoding** – Serializes the entire configuration structure as `newCfg`
4. **Short-circuit optimization** – Compares the new JSON against `rawCfgJSON`; if identical and `forceReload` is false, returns `errSameConfig` to skip unnecessary reloads
5. **ID indexing** – Walks the config tree to build an ID-to-path index via `indexConfigObjects` for efficient module lookups
6. **Decode and provision** – Invokes `unsyncedDecodeAndRun(newCfg, true)` to instantiate modules and run validation
7. **Rollback protection** – If provisioning fails, unmarshals the previous raw config back into `rawCfg` to maintain server stability
8. **Persistence** – Stores the new JSON in `rawCfgJSON` for future comparison

## Provisioning and Validation

### Module Provisioning

Inside [`caddy.go`](https://github.com/caddyserver/caddy/blob/main/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`](https://github.com/caddyserver/caddy/blob/main/modules.go)【[Validator interface](https://github.com/caddyserver/caddy/blob/master/modules.go#L47-L53)】. Any module requiring additional validation implements the `Validate()` method:

- **TLS application** – [`modules/caddytls/tls.go`](https://github.com/caddyserver/caddy/blob/main/modules/caddytls/tls.go) contains `TLS.Validate` to check certificate configurations【[TLS.Validate](https://github.com/caddyserver/caddy/blob/master/modules/caddytls/tls.go#L367-L371)】
- **HTTP handlers** – Various handlers in [`modules/caddyhttp/handlers.go`](https://github.com/caddyserver/caddy/blob/main/modules/caddyhttp/handlers.go) implement `Validate` to verify routing logic
- **Logging filters** – [`modules/logging/filters.go`](https://github.com/caddyserver/caddy/blob/main/modules/logging/filters.go) includes `MultiRegexpFilter.Validate` to check regular expression syntax【[MultiRegexpFilter.Validate](https://github.com/caddyserver/caddy/blob/master/modules/logging/filters.go#L652-L660)】

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`](https://github.com/caddyserver/caddy/blob/main/cmd/commandfuncs.go) provides explicit validation without starting the server【[cmdValidateConfig](https://github.com/caddyserver/caddy/blob/master/cmd/commandfuncs.go#L618-L659)】. 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](https://github.com/caddyserver/caddy/blob/master/caddy.go#L35-L43)】.

## Practical Configuration Examples

### Loading via Admin API

Send a Caddyfile directly to the running server for hot-reloading:

```bash
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:

```go
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:

```bash
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 in [`caddyconfig/load.go`](https://github.com/caddyserver/caddy/blob/main/caddyconfig/load.go) and [`cmd/main.go`](https://github.com/caddyserver/caddy/blob/main/cmd/main.go)
- **The `changeConfig` function** in [`caddy.go`](https://github.com/caddyserver/caddy/blob/main/caddy.go) orchestrates 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 `Validator` interface** in [`modules.go`](https://github.com/caddyserver/caddy/blob/main/modules.go), where modules like TLS and HTTP handlers implement `Validate()` 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`](https://github.com/caddyserver/caddy/blob/main/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`](https://github.com/caddyserver/caddy/blob/main/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.