gofiber/fiber · error

limiter: failed to unmarshal key %q: %w

Error message

limiter: failed to unmarshal key %q: %w

What it means

Returned by the limiter middleware's storage-backed manager.get when raw bytes retrieved from the external fiber.Storage cannot be deserialized via msgp (UnmarshalMsg) into the internal *item struct (currHits/prevHits/exp). The limiter serializes hit counters with MessagePack, so this error means the bytes under the key are not valid msgp or do not match the item schema.

Source

Thrown at middleware/limiter/manager.go:76

func (m *manager) release(e *item) {
	e.prevHits = 0
	e.currHits = 0
	e.exp = 0
	m.pool.Put(e)
}

// get data from storage or memory
func (m *manager) get(ctx context.Context, key string) (*item, error) {
	if m.storage != nil {
		raw, err := m.storage.GetWithContext(ctx, key)
		if err != nil {
			return nil, fmt.Errorf("limiter: failed to get key %q from storage: %w", m.logKey(key), err)
		}
		if raw != nil {
			it := m.acquire()
			if _, err := it.UnmarshalMsg(raw); err != nil {
				m.release(it)
				return nil, fmt.Errorf("limiter: failed to unmarshal key %q: %w", m.logKey(key), err)
			}
			return it, nil
		}
		return m.acquire(), nil
	}

	value := m.memory.Get(key)
	if value == nil {
		return m.acquire(), nil
	}

	it, ok := value.(*item)
	if !ok {
		return nil, fmt.Errorf("limiter: unexpected entry type %T for key %q", value, m.logKey(key))
	}

	return it, nil
}

View on GitHub (pinned to 9a4c7e57fe)

Solutions

  1. Flush or namespace-isolate the storage keys used by the limiter after upgrading fiber versions (msgp payload is not version-tolerant).
  2. Ensure no other application writes to the limiter's key namespace.
  3. If using Redis, inspect the value under the reported key and confirm it is the expected msgp blob, not corrupt or plaintext.
  4. Pin all instances sharing the storage to a single fiber version.

Example fix

// before: colliding keys across versions
app.Use(limiter.New(limiter.Config{
    Storage: redisStore, // default key namespace shared with old version
}))

// after: isolate keys per deployment version
app.Use(limiter.New(limiter.Config{
    Storage: redisStore,
    KeyGenerator: func(c fiber.Ctx) string {
        return "v2:" + c.IP()
    },
}))
Defensive patterns

Strategy: validation

Validate before calling

// Before wiring the limiter, confirm storage is healthy and isolated.
// Run once at startup:
func checkLimiterStorage(s fiber.Storage) error {
    const probe = "limiter:healthcheck"
    if err := s.Set(probe, []byte{0}, time.Second); err != nil {
        return fmt.Errorf("storage unhealthy: %w", err)
    }
    _ = s.Delete(probe)
    return nil
}

Try / catch

// In a fiber error handler, surface limiter storage corruption as a 503
// rather than a generic 500, so clients retry.
app := fiber.New(fiber.Config{
    ErrorHandler: func(c fiber.Ctx, err error) error {
        var msg string = err.Error()
        if strings.Contains(msg, "limiter: failed to unmarshal key") {
            return c.Status(fiber.StatusServiceUnavailable).SendString("rate store unavailable")
        }
        return c.Status(fiber.StatusInternalServerError).SendString(err.Error())
    },
})

Prevention

When it happens

Trigger: Limiter is configured with a non-nil fiber.Storage; storage.GetWithContext returns non-nil bytes but it.UnmarshalMsg(raw) fails at manager.go:74. Happens when stored data was written by an incompatible limiter version whose msgp-generated schema differs, when another process wrote arbitrary bytes to the same key, or when the storage backend corrupted the value.

Common situations: Upgrading gofiber/fiber across versions where the internal item struct field layout changed; sharing a Redis keyspace between two apps that collide on limiter keys; storage corruption after a Redis crash/restart restore; running two different fiber major versions against the same storage backend.

Related errors


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