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
- Check both wrapped errors to identify the storage fault (permissions vs connectivity).
- Restore read/write storage access, remove the reported folder if needed, restart.
- 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
- Custom storage modules must map 'missing key' to fs.ErrNotExist exactly.
- Keep meta.json writable and delete-capable.
- Do not change permissions on individual files within config folders.
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
- protocol argument was not a string
- %s is invalid policy
- ErrInvalidSplitPath
- private key does not match issuer public key
- --input is required
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/eb79b1aeee050a05.
Report an issue: GitHub.