caddyserver/caddy · error

caddy responded with error: HTTP %d: %s

Error message

caddy responded with error: HTTP %d: %s

What it means

The admin API responded with a 4xx/5xx status and this message surfaces that status plus the first 2 MiB of the body. It is the CLI's generic relay of server-side errors from operations like loading a config (caddy reload / caddy load), stopping, or reading endpoints.

Source

Thrown at cmd/commandfuncs.go:850

		Transport: &http.Transport{
			DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
				return net.Dial(parsedAddr.Network, parsedAddr.JoinHostPort(0))
			},
		},
	}

	resp, err := client.Do(req) //nolint:gosec // the only SSRF here would be self-sabotage I think
	if err != nil {
		return nil, fmt.Errorf("performing request: %v", err)
	}

	// if it didn't work, let the user know
	if resp.StatusCode >= 400 {
		respBody, err := io.ReadAll(io.LimitReader(resp.Body, 1024*1024*2))
		if err != nil {
			return nil, fmt.Errorf("HTTP %d: reading error message: %v", resp.StatusCode, err)
		}
		return nil, fmt.Errorf("caddy responded with error: HTTP %d: %s", resp.StatusCode, respBody)
	}

	return resp, nil
}

// DetermineAdminAPIAddress determines which admin API endpoint address should
// be used based on the inputs. By priority: if `address` is specified, then
// it is returned; if `config` is specified, then that config will be used for
// finding the admin address; if `configFile` (and `configAdapter`) are specified,
// then that config will be loaded to find the admin address; otherwise, the
// default admin listen address will be returned.
func DetermineAdminAPIAddress(address string, config []byte, configFile, configAdapter string) (string, error) {
	// Prefer the address if specified and non-empty
	if address != "" {
		return address, nil
	}

	// Try to load the config from file if specified, with the given adapter name

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Read the body text in the message — it names the exact server-side rejection cause
  2. Validate the config first with: caddy validate --config <file>
  3. Check the daemon logs for the stack behind the 4xx/5xx
  4. Ensure the running binary includes the modules your config references (caddy list-modules)

Example fix

# before
caddy reload --config Caddyfile   # fails: caddy responded with error: HTTP 500 ...

# after (catch config problems before reloading)
caddy validate --config Caddyfile && caddy adapt --config Caddyfile --adapter caddyfile | jq .
caddy reload --config Caddyfile
Defensive patterns

Strategy: validation

Validate before calling

# Validate before applying, so server-side 4xx/5xx never happens:
caddy validate --config /etc/caddy/Caddyfile

Try / catch

err := reload(); if err != nil && strings.Contains(err.Error(), "caddy responded with error: HTTP 4") { /* config rejected: surface body, keep previous config running */ }

Prevention

When it happens

Trigger: 'caddy reload' with a config the running instance rejects (bad module names, invalid site addresses, provisioning failures); 'caddy stop' when already stopped (404 on /stop semantics); requesting a config path that does not exist.

Common situations: A Caddyfile that adapts fine but fails provisioning at runtime (e.g. TLS problem, port already bound by another process, unresolved module); reloading against a Caddy version lacking a module used in the config.

Related errors


AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15). Data as JSON: /api/errors/5c71ecd26346f52a. Report an issue: GitHub.