caddyserver/caddy · error · APIError

WebSocket connections aren't allowed.

Error message

WebSocket connections aren't allowed.

What it means

The distributed STEK provider could not obtain the exclusive storage lock ('stek' lock) before reading or creating the shared TLS session ticket keys. Caddy uses certmagic Storage.Lock to coordinate STEK generation across a cluster, so every instance serializes through this lock. The wrapped error comes from the configured storage module (file system, Redis, S3, etc.).

Source

Thrown at admin.go:828

// be called more than once per request, for example if a request
// is rewritten (i.e. internal redirect).
func (h adminHandler) serveHTTP(w http.ResponseWriter, r *http.Request) {
	if h.remoteControl != nil {
		// enforce access controls on secure endpoint
		if err := h.remoteControl.enforceAccessControls(r); err != nil {
			h.handleError(w, r, err)
			return
		}
	}

	// common mitigations in browser contexts
	if strings.Contains(r.Header.Get("Upgrade"), "websocket") {
		// I've never been able demonstrate a vulnerability myself, but apparently
		// WebSocket connections originating from browsers aren't subject to CORS
		// restrictions, so we'll just be on the safe side
		h.handleError(w, r, APIError{
			HTTPStatus: http.StatusBadRequest,
			Err:        errors.New("websocket connections aren't allowed"),
			Message:    "WebSocket connections aren't allowed.",
		})
		return
	}
	if strings.Contains(r.Header.Get("Sec-Fetch-Mode"), "no-cors") {
		// turns out web pages can just disable the same-origin policy (!???!?)
		// but at least browsers let us know that's the case, holy heck
		h.handleError(w, r, APIError{
			HTTPStatus: http.StatusBadRequest,
			Err:        errors.New("client attempted to make request by disabling same-origin policy using no-cors mode"),
			Message:    "Disabling same-origin restrictions is not allowed.",
		})
		return
	}
	if r.Header.Get("Origin") == "null" {
		// bug in Firefox in certain cross-origin situations (yikes?)
		// (not strictly a security vuln on its own, but it's red flaggy,
		// since it seems to manifest in cross-origin contexts)

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Check the wrapped error text to identify which storage backend failed and address that directly (connectivity, credentials, permissions).
  2. If using local file storage, verify write permissions on Caddy's data directory (default $XDG_DATA_HOME/caddy) and remove stale lock files after confirming no instance is running.
  3. If a remote storage (Redis/S3/etc.) is configured, verify network reachability, credentials, and that the storage module implements distributed locking correctly.
  4. If another instance is legitimately holding the lock for a long time (slow storage), increase the storage lock timeout or speed up the storage backend.
  5. Restart the affected Caddy instance once the underlying storage issue is fixed; STEK locking retries on the next provisioning attempt.

Example fix

// before: default file storage with permission issues
caddy run --config Caddyfile
# fix: ensure data dir is writable
sudo chown -R caddy:caddy /var/lib/caddy

// after: or point storage at a shared backend for clusters
{
  storage redis {
    host <redis-host>
  }
}
Defensive patterns

Strategy: retry

Validate before calling

// Before starting a cluster, verify the storage lock works:
err := storage.Lock(ctx, "stek")
if err != nil {
    log.Fatalf("storage locking unavailable: %v", err)
}
storage.Unlock(ctx, "stek")

Try / catch

In Go, treat this as a provisioning error: log it, surface it via startup failure, and retry with backoff rather than serving with uncoordinated keys: if err := provider.Initialize(cfg); err != nil { logger.Error("stek init failed; retrying", zap.Error(err)); time.Sleep(backoff); continue }

Prevention

When it happens

Trigger: Provider.getSTEK() calls s.storage.Lock(s.ctx, stekLockName) and it returns an error: another Caddy instance holds the lock longer than the storage's timeout, the storage backend is unreachable (Redis/S3 outage), file permissions deny creating the lock file, or a stale lock from a crashed instance was never cleaned up.

Common situations: Clusters where one node stalls while holding the lock; shared storage mounted read-only or with wrong ownership; leftover lock files after a hard kill (non-graceful shutdown); network partition between Caddy nodes and a remote storage backend; locking not supported properly by a third-party storage module.

Related errors


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