caddyserver/caddy · error

depleted attempts to find an available config_id

Error message

depleted attempts to find an available config_id

What it means

ECH config IDs are a single byte (0-255). newECHConfigID does rejection sampling: up to 256 random draws, skipping IDs whose ech/configs/<id> path already Exists in storage. If every distinct ID was either tried-and-taken (or context cancelled, which is returned separately), allocation gives up with this error. In practice it means storage holds entries for (nearly) all 256 IDs — usually accumulated stale/orphaned configs from rotations or partial writes.

Source

Thrown at modules/caddytls/ech.go:1143

			continue
		}
		tried[num] = true

		// check to see if any of the subkeys use this config ID
		numStr := strconv.Itoa(int(num))
		trialPath := path.Join(echConfigsKey, numStr)
		if ctx.Storage().Exists(ctx, trialPath) {
			continue
		}

		return num, nil
	}

	if err := ctx.Err(); err != nil {
		return 0, err
	}

	return 0, fmt.Errorf("depleted attempts to find an available config_id")
}

// ECHPublisher is an interface for publishing ECHConfigList values
// so that they can be used by clients.
type ECHPublisher interface {
	// Returns a key that is unique to this publisher and its configuration.
	// A publisher's ID combined with its config is a valid key.
	// It is used to prevent duplicating publications.
	PublisherKey() string

	// Publishes the ECH config list (as binary) for the given innerNames. Some
	// publishers may not need a list of inner/protected names, and can ignore the
	// argument; most, however, will want to use it to know which inner names are
	// to be associated with the given ECH config list.
	//
	// Implementations should return an error of type PublishECHConfigListErrors
	// when relevant to key errors to their associated innerName, but should never
	// return a non-nil PublishECHConfigListErrors when its length is 0.

View on GitHub (pinned to 50e54ee279)

Solutions

  1. List ech/configs/ in storage and delete stale entries (those with old Created timestamps in meta.json, or directories missing config.bin/meta.json).
  2. If rotation naturally fills the space, prune expired ECH configs as part of maintenance.
  3. Verify no process is repeatedly creating partial ECH configs (check for the per-write store errors) and fix that root cause.
  4. Restart Caddy after cleanup so provisioning retries ID allocation.

Example fix

# before: all 256 IDs occupied
$ ls ~/.local/share/caddy/ech/configs | wc -l
256

# after: remove stale/partial entries (keep recent ones)
$ find ~/.local/share/caddy/ech/configs -name meta.json -mtime +30 \
    -exec dirname {} \; | xargs rm -rf
Defensive patterns

Strategy: validation

Validate before calling

// Maintenance check: count occupied ECH config IDs and prune stale ones.
func echIDUsage(ctx context.Context, stor caddy.Storage) (int, error) {
    entries, err := stor.List(ctx, "ech/configs", false)
    if err != nil {
        return 0, err
    }
    return len(entries), nil
}

Try / catch

if err != nil && strings.Contains(err.Error(), "depleted attempts to find an available config_id") {
    // storage has (nearly) all 256 IDs taken: prune ech/configs/* and restart
}

Prevention

When it happens

Trigger: The ech/configs/ prefix in storage contains subdirectories for all 256 possible numeric IDs — e.g. after many ECH rotations without cleanup, or repeated failed provisioning runs that each stranded partial entries (key.bin written but later step failed).

Common situations: Long-running Caddy with ECH on and rotation creating new configs each interval; storage littered by crash-looping during provisioning; copying a cluttered storage between environments.

Related errors


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