Redis Keys Not Expiring: Common TTL Mistakes and What to Check
Dev
Last updated on

Redis Keys Not Expiring: Common TTL Mistakes and What to Check


If Redis keys are not expiring, the problem is usually not Redis “forgetting” TTL. It is usually an application write path resetting the key, skipping EXPIRE, or assuming a key is still volatile when it is no longer.

This guide focuses on the fastest checks first: verify whether the key still has a TTL, confirm which command path writes it, and look for overwrite patterns that silently remove expiration.


Quick Answer

If Redis keys are not expiring, first check whether the key still has a TTL at all.

In many incidents, the real cause is a later SET or overwrite command that cleared the timeout, an EXPIRE path that never succeeded, or multiple writers making different assumptions about the same key.

What to Check First

Use this order first:

  1. inspect TTL or PTTL on the real key
  2. confirm whether the key shows -1, -2, or a positive TTL
  3. find every code path that writes the key
  4. check whether a later overwrite clears expiration
  5. inspect whether EXPIRE uses conditional flags like NX or XX

If the TTL is already -1, you are usually debugging the write path, not Redis expiration itself.

What to check first

Start with the actual key state instead of the application code.

Run:

TTL your:key

and if needed:

PTTL your:key

A TTL of -1 means the key exists but has no expiration. A TTL of -2 means the key no longer exists.

That distinction matters because “not expiring” and “already gone” are different problems.

The most common mistake: overwriting the key

Redis documents that commands which delete or overwrite the contents of a key clear the timeout. That includes commands such as SET and GETSET.

A very common failure pattern looks like this:

  1. the app creates a key
  2. the app sets EXPIRE
  3. a later code path writes the key again with SET
  4. the TTL disappears

If your TTL suddenly becomes -1, this is the first thing to suspect.

Check whether EXPIRE is actually succeeding

EXPIRE returns 1 if the timeout was set and 0 if it was not.

That means a safe debugging path is:

SET mykey value
EXPIRE mykey 60
TTL mykey

If your application never checks the result of EXPIRE, a failed conditional mode such as XX or NX can silently leave the key without a TTL.

Watch for conditional expiration modes

Redis supports NX, XX, GT, and LT options on EXPIRE.

These are useful, but they also create easy debugging traps:

  • XX only sets TTL when the key already has an expiry
  • NX only sets TTL when the key has no expiry
  • GT only sets TTL when the new expiry is greater
  • LT only sets TTL when the new expiry is smaller

If a team adds one of these modes and later forgets it, the key can keep existing with no updated TTL even though the code “looks” correct.

TTL state versus write-path bug

TTL state or patternWhat it usually meansBetter next step
TTL = -1Key exists without expirationFind overwrite or missing EXPIRE path
TTL = -2Key is already goneStop debugging expiration and check writers
TTL keeps returning to a large valueAnother writer keeps refreshing the keyFind all refresh and cache warmer paths
TTL disappears after SETOverwrite cleared timeoutUse SET ... EX or restore expiry explicitly

Verify the actual write pattern, not just one code path

Many TTL bugs happen because there is more than one writer.

Typical examples:

  • a request handler sets the TTL correctly
  • a background job rewrites the same key without expiration
  • a cache warmer refreshes the value but not the TTL
  • a session refresh path assumes the TTL survives every write

If you already know your Redis usage is getting larger over time, compare that behavior with Redis Memory Usage High. If the incident also involves disappearing keys under pressure, compare it with Redis Eviction Policy Guide.

Use the simplest reproducible test

Before blaming the full app, recreate the behavior with the exact command sequence.

Example:

SET mykey value
EXPIRE mykey 30
TTL mykey
SET mykey newvalue
TTL mykey

If the second TTL becomes -1, Redis is doing exactly what the docs describe. The fix belongs in the application write pattern, not the server.

A safer application pattern

If the value and expiry should always be created together, set them together.

For string keys, a pattern like this is safer than separate write steps:

SET mykey value EX 60

That reduces the chance that a later refactor forgets the second command.

Quick troubleshooting checklist

  1. confirm whether TTL is -1, -2, or a positive value
  2. find every code path that writes the key
  3. check whether SET or another overwrite command runs after EXPIRE
  4. inspect whether EXPIRE uses NX, XX, GT, or LT
  5. test the write sequence directly in Redis

If most checks point to application behavior, treat this as a write-path bug, not a Redis scheduler bug.

Symptom shortcut

  • Start here if cache or session keys stay around long after they should have expired.
  • If memory growth is the first visible symptom, compare with the memory guide too.

Quick commands

redis-cli TTL your:key
redis-cli PTTL your:key
redis-cli OBJECT idletime your:key

Use these to check whether the key still has expiration, whether expiry is close, and whether the key is actually being touched again.

Look for -1 TTL values, keys that keep getting their TTL reset, and idle time that never grows because another writer keeps overwriting the key.

Bottom Line

Redis usually is not forgetting TTL on its own. The more common story is that the application rewrote the key, skipped expiration, or had multiple writers with inconsistent assumptions.

In practice, start with the real TTL state, then trace every write path touching the key. Once you know where the timeout disappears, the fix is usually straightforward.

FAQ

Q. Why does TTL return -1?

Because the key exists but currently has no expiration attached to it.

Q. Does SET keep the old TTL?

Not in the common overwrite case. Redis documents that overwriting commands clear the timeout.

Q. What is the safest way to create a key with expiration?

Use a command pattern that writes the value and expiry together when possible.

Sources:

Start Here

Continue with the core guides that pull steady search traffic.