caddyserver/caddy · warning
stopping admin server: %ds timeout
Error message
stopping admin server: %ds timeout
What it means
This message is the cause attached to the 10-second context given to http.Server.Shutdown when stopping the previous admin server. If graceful shutdown exceeds 10s (a client holding a connection open, a slow streaming request on /metrics or /load), the context expires and this cause is surfaced so the operator sees it was a timeout, not a random failure.
Source
Thrown at admin.go:743
func adminPathAllowed(reqPath, allowedPath string) bool {
if allowedPath == "" || allowedPath == "/" {
return strings.HasPrefix(reqPath, allowedPath)
}
if reqPath == allowedPath {
return true
}
if strings.HasSuffix(allowedPath, "/") {
return strings.HasPrefix(reqPath, allowedPath)
}
return strings.HasPrefix(reqPath, allowedPath+"/")
}
func stopAdminServer(srv *http.Server) error {
if srv == nil {
return fmt.Errorf("no admin server")
}
timeout := 10 * time.Second
ctx, cancel := context.WithTimeoutCause(context.Background(), timeout, fmt.Errorf("stopping admin server: %ds timeout", int(timeout.Seconds())))
defer cancel()
err := srv.Shutdown(ctx)
if err != nil {
if cause := context.Cause(ctx); cause != nil && errors.Is(err, context.DeadlineExceeded) {
err = cause
}
return fmt.Errorf("shutting down admin server: %v", err)
}
Log().Named("admin").Info("stopped previous server", zap.String("address", srv.Addr))
return nil
}
// AdminRouter is a type which can return routes for the admin API.
type AdminRouter interface {
Routes() []AdminRoute
}
// AdminRoute represents a route for the admin endpoint.View on GitHub (pinned to 50e54ee279)
Solutions
- Close or shorten long-lived connections to the admin endpoint (raise scrape_interval/timeout sanity, close idle clients)
- Avoid hammering /load with concurrent changes; wait for one change to settle
- If it recurs with no obvious client, capture which connection blocks shutdown (ss -tnp against the admin port) and fix that client
Defensive patterns
Strategy: retry
Prevention
- Close long-lived connections to the admin endpoint before config changes
- Pause scrapes and health checks during reloads
- Retry the config change once after clearing idle admin connections
When it happens
Trigger: A long-lived HTTP connection to the admin endpoint (e.g. an open metrics scrape, SSE, or a stalled POST /load) that does not finish within the 10s graceful window while a config change replaces the admin server.
Common situations: Prometheus scraping /metrics with a slow exporter; scripts that open keep-alive connections to localhost:2019 and never close them; frequent config changes in quick succession.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- shutting down admin server: %v
- no admin server
- request path is missing object ID
- malformed object path
- unknown object ID '%s'
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/8ea740c427539b96.
Report an issue: GitHub.