caddyserver/caddy · error

HTTP %d: reading error message: %v

Error message

HTTP %d: reading error message: %v

What it means

The admin endpoint answered with HTTP >= 400, but while reading the (capped at 2 MiB) error response body the read itself failed. This is a secondary failure: the transport broke mid-response, so the CLI can report neither the status body nor succeed.

Source

Thrown at cmd/commandfuncs.go:848

	// expects; reuse is not of particular concern here
	client := http.Client{
		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
	}

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Retry the CLI command once the daemon is stable
  2. Check for two processes fighting over the same admin endpoint
  3. If reproducible, capture daemon logs around the request to find why the body stream was cut
Defensive patterns

Strategy: retry

Try / catch

if err != nil && strings.Contains(err.Error(), "reading error message") { time.Sleep(time.Second); err = retryCmd() }

Prevention

When it happens

Trigger: The admin connection is severed after headers are sent — daemon killed mid-request, unix socket removed during the call, or a proxy in between closing the stream.

Common situations: Rare in practice; seen when the Caddy process is being torn down concurrently with a CLI call, or when something between the CLI and the daemon truncates responses.

Related errors


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