gofiber/fiber · warning

cache: failed to delete cached response for key %q: %w

Error message

cache: failed to delete cached response for key %q: %w

What it means

On the response-write path: an existing cached entry exists and the new response carries Cache-Control: private, no-cache, or Vary: *, all of which make it uncacheable. Fiber tries to delete the old entry to honor RFC 9111 and deleteKey failed. The response is still sent to the client (c.Set CacheHeader=cacheUnreachable) but the stale entry remains in storage.

Source

Thrown at middleware/cache/cache.go:538

		hasPrivate := respCacheControl.hasPrivate
		hasNoCache := respCacheControl.hasNoCache
		varyNames, varyHasStar := parseVary(varyHeader)

		// Respect server cache-control: no-store
		if respCacheControl.hasNoStore {
			c.Set(cfg.CacheHeader, cacheUnreachable)
			return nil
		}

		// RFC 9111 requires responses with Vary: * to remain uncacheable even when
		// response-driven Vary partitioning is otherwise disabled.
		if hasPrivate || hasNoCache || varyHasStar {
			if e != nil {
				if err := deleteKey(reqCtx, key); err != nil {
					if cfg.Storage != nil {
						manager.release(e)
					}
					return fmt.Errorf("cache: failed to delete cached response for key %q: %w", maskKey(key), err)
				}
				mux.Lock()
				removeHeapEntry(key, e.heapidx)
				if cfg.Storage != nil {
					manager.release(e)
				}
				e = nil
				mux.Unlock()
			}

			if !cfg.DisableVaryHeaders && hasVaryManifest {
				if err := manager.del(reqCtx, manifestKey); err != nil {
					return fmt.Errorf("cache: failed to delete stale vary manifest %q: %w", maskKey(manifestKey), err)
				}
			}

			c.Set(cfg.CacheHeader, cacheUnreachable)
			return nil

View on GitHub (pinned to 9a4c7e57fe)

Solutions

  1. Resolve the underlying storage Delete failure (see wrapped %w).
  2. Flush the storage so previously-cached entries that should no longer be cached are removed out of band.
  3. Align upstream cache-control so it doesn't oscillate between cacheable and private/no-cache for the same route.
  4. Consider cfg.Next to bypass caching for known-private routes, removing the delete-on-conflict path entirely.

Example fix

// before: storage Delete fails purging old entry on private response
cfg := cache.Config{Storage: flakyStorage}

// after: skip cache for private routes + robust storage
cfg := cache.Config{
  Storage: robustStorage,
  Next: func(c fiber.Ctx) bool {
    return c.Path() == "/me" // known-private route
  },
}
Defensive patterns

Strategy: validation

Validate before calling

// Skip caching routes that frequently return private/no-cache.
cfg := cache.Config{
  Next: func(c fiber.Ctx) bool {
    return c.Path() == "/login" || c.Path() == "/account"
  },
}

Type guard

null

Try / catch

app.Use(func(c fiber.Ctx) error {
    err := c.Next()
    if err != nil && strings.Contains(err.Error(), "failed to delete cached response") {
        log.Warnf("uncacheable-response purge failed: %v", err)
        return nil
    }
    return err
})

Prevention

When it happens

Trigger: A cached entry exists for a key; the origin now returns 'Cache-Control: private' (or 'no-cache' / 'Vary: *') for that key; deleteKey is called and manager.del returns an error. Triggered by upstream cache-control policy changes coinciding with a storage Delete failure.

Common situations: Origin tightens its cache-control (login-gated content); two responses racing for the same key where one is private; storage backend transiently unavailable; switching storage drivers without flushing the old entries.

Related errors


AI-assisted analysis of gofiber/fiber@9a4c7e57fe (2026-08-04). Data as JSON: /data/errors/dcfd2e94b80aacf1.json. Report an issue: GitHub.