{"record":{"id":"8ea740c427539b96","repo":"caddyserver/caddy","slug":"stopping-admin-server-ds-timeout","errorCode":null,"errorMessage":"stopping admin server: %ds timeout","messagePattern":"stopping admin server: (.+?)s timeout","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"admin.go","lineNumber":743,"sourceCode":"func adminPathAllowed(reqPath, allowedPath string) bool {\n\tif allowedPath == \"\" || allowedPath == \"/\" {\n\t\treturn strings.HasPrefix(reqPath, allowedPath)\n\t}\n\tif reqPath == allowedPath {\n\t\treturn true\n\t}\n\tif strings.HasSuffix(allowedPath, \"/\") {\n\t\treturn strings.HasPrefix(reqPath, allowedPath)\n\t}\n\treturn strings.HasPrefix(reqPath, allowedPath+\"/\")\n}\n\nfunc stopAdminServer(srv *http.Server) error {\n\tif srv == nil {\n\t\treturn fmt.Errorf(\"no admin server\")\n\t}\n\ttimeout := 10 * time.Second\n\tctx, cancel := context.WithTimeoutCause(context.Background(), timeout, fmt.Errorf(\"stopping admin server: %ds timeout\", int(timeout.Seconds())))\n\tdefer cancel()\n\terr := srv.Shutdown(ctx)\n\tif err != nil {\n\t\tif cause := context.Cause(ctx); cause != nil && errors.Is(err, context.DeadlineExceeded) {\n\t\t\terr = cause\n\t\t}\n\t\treturn fmt.Errorf(\"shutting down admin server: %v\", err)\n\t}\n\tLog().Named(\"admin\").Info(\"stopped previous server\", zap.String(\"address\", srv.Addr))\n\treturn nil\n}\n\n// AdminRouter is a type which can return routes for the admin API.\ntype AdminRouter interface {\n\tRoutes() []AdminRoute\n}\n\n// AdminRoute represents a route for the admin endpoint.","sourceCodeStart":725,"sourceCodeEnd":761,"githubUrl":"https://github.com/caddyserver/caddy/blob/50e54ee279aa1e504fe218ca49ab6ae16c100410/admin.go#L725-L761","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":["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"],"tags":["admin-api","shutdown","timeout","http"],"backgroundTag":null,"analyzedSha":"50e54ee279aa1e504fe218ca49ab6ae16c100410","analyzedAt":"2026-08-15T09:20:21.641Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}