caddyserver/caddy · warning

shutting down admin server: %v

Error message

shutting down admin server: %v

What it means

Wrapper returned when http.Server.Shutdown of the admin endpoint fails for any reason (after substituting the timeout cause if applicable). The %v carries the underlying net/http error; the most common value is context deadline exceeded, i.e. the graceful stop didn't finish in 10 seconds.

Source

Thrown at admin.go:750

	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.
type AdminRoute struct {
	Pattern string
	Handler AdminHandler
}

type adminHandler struct {
	mux *http.ServeMux

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Inspect the wrapped error text — 'context deadline exceeded' means clients held connections: find and close them
  2. Reduce admin endpoint traffic during config changes (pause scrapes/health checks during deploy)
  3. If it persists, report upstream with the inner error and connection dump
Defensive patterns

Strategy: retry

Try / catch

if err := changeConfig(http.MethodPost, "/load", newCfg, "", false); err != nil {
    var apiErr caddy.APIError
    if errors.As(err, &apiErr) && strings.Contains(apiErr.Error(), "shutting down admin server") {
        time.Sleep(time.Second)
        // one retry after old connections drain
        err = changeConfig(http.MethodPost, "/load", newCfg, "", false)
    }
    if err != nil {
        log.Fatal(err)
    }
}

Prevention

When it happens

Trigger: Config change forcing the old admin server to stop while a request or connection is still active past the 10s budget; shutdown erroring for OS-level reasons (listener already closed, FD issues).

Common situations: Hanging clients on the admin port; repeated rapid config loads; containers with aggressive health-check polling of the admin endpoint during reload.

Related errors


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