caddyserver/caddy · error · HandlerError

rfc9112 forbids empty Host

Error message

rfc9112 forbids empty Host

What it means

loadECHConfig could not read the metadata file (meta.json) and the error was not fs.ErrNotExist (missing files are tolerated with a warning and recreated later), so it attempted cleanup — deleting the config folder — and that delete failed too. This is the double-failure variant for the metadata load path.

Source

Thrown at modules/caddyhttp/server.go:696

	if len(r.Method) > 32 {
		if s.shouldLogRequest(r) {
			if c := s.accessLogger.Check(zapcore.DebugLevel, "rejecting request with long method"); c != nil {
				c.Write(
					zap.String("method_trunc", r.Method[:32]),
					zap.String("remote_addr", r.RemoteAddr),
				)
			}
		}
		return HandlerError{StatusCode: http.StatusMethodNotAllowed}
	}

	// RFC 9112 section 3.2: "A server MUST respond with a 400 (Bad Request) status
	// code to any HTTP/1.1 request message that lacks a Host header field and to any
	// request message that contains more than one Host header field line or a Host
	// header field with an invalid field value."
	if r.ProtoMajor == 1 && r.ProtoMinor == 1 && r.Host == "" {
		return HandlerError{
			Err:        errors.New("rfc9112 forbids empty Host"),
			StatusCode: http.StatusBadRequest,
		}
	}

	// Drop headers whose names contain `_` or `.`: once FastCGI/CGI/FrankenPHP etc.
	// rewrites `-` to `_` (and PHP additionally folds `.` to `_` when registering
	// $_SERVER keys), an underscore or dot alias collides with the legitimate
	// hyphenated header and can bypass `forward_auth copy_headers`
	// (GHSA-f59h-q822-g45g, GHSA-49wc-4hcv-v58q).
	//
	// The two allowlists (ExpectedUnderscoreHeaders, ExpectedDotHeaders) are
	// otherwise independent: each only keeps headers spelled with its own
	// character, and either allowlist actively drops the plain-hyphenated
	// variant of its entries.
	for k := range r.Header {
		hasUnderscore := strings.ContainsRune(k, '_')
		hasDot := strings.ContainsRune(k, '.')

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Check both wrapped errors to identify the storage fault (permissions vs connectivity).
  2. Restore read/write storage access, remove the reported folder if needed, restart.
  3. Note: a simply-missing meta.json is fine (warns and recreates) — only non-ErrNotExist load errors trigger this path.
Defensive patterns

Strategy: validation

Type guard

// When implementing custom storage, return fs.ErrNotExist for missing keys so Caddy's tolerant path engages:
func (s customStorage) Load(ctx context.Context, key string) ([]byte, error) {
    b, err := s.get(key)
    if os.IsNotExist(err) { return nil, fs.ErrNotExist }
    return b, err
}

Try / catch

if err != nil && !errors.Is(err, fs.ErrNotExist) { // real failure: fix storage, delete named folder, restart } — remember missing meta.json alone is fine (warned, recreated).

Prevention

When it happens

Trigger: storage.Load of meta.json returns a non-ErrNotExist error (permission denied, backend I/O error) AND storage.Delete of the folder fails (read-only storage, outage).

Common situations: Permissions changed on meta.json only; partially failed storage where reads error; read-only volumes blocking the cleanup delete.

Related errors


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