Fixing the Redis "ERR invalid expire time" Error with Zero or Negative TTL

beginnerπŸ”΄ Redis2026-06-18| Redis (versions 5.0 through 7.x) running on Linux, macOS, or Docker.

Error Message

(error) ERR invalid expire time in 'set' command
#redis#ttl#caching#backend#ioredis

The Error Scenario

While building a session cache for a Node.js service, I ran into a bug that only appeared when users reached the very end of their login period. My code calculated the TTL (Time To Live) dynamically by subtracting the current time from the session's expiry timestamp. During high-traffic testing, everything seemed fine, but as soon as a session had only milliseconds left, the logs started filling up with this:

(error) ERR invalid expire time in 'set' command

You can easily trigger this error in the Redis CLI. If you try to use SET with an EX (seconds) or PX (milliseconds) argument of zero or less, Redis refuses the operation entirely.

# This fails because 0 is not a positive integer
redis> SET session:user:123 "data" EX 0
(error) ERR invalid expire time in 'set' command

# Negative values also trigger the error
redis> SET session:user:123 "data" EX -10
(error) ERR invalid expire time in 'set' command

Analysis: Why the SET Command is Strict

Redis requires the EX and PX options to be positive integers. This is a common trap for developers because the standalone EXPIRE command is much more lenient. Running EXPIRE key 0 simply deletes the key. However, SET ... EX is an atomic operation. Redis enforces stricter validation here to prevent ambiguous commands from being partially executed.

Most crashes occur when the TTL math looks like this:

# Python example
ttl = target_expiry - current_time
# If current_time >= target_expiry, ttl is 0 or negative
redis_client.set("session_key", "value", ex=ttl)

Even a 5ms delay in your application logic can turn a positive TTL into a zero. This causes your entire database write to fail instead of just expiring the data gracefully.

The Difference Between SET EX and EXPIRE

It helps to think of SET ... EX as a creation command and EXPIRE as a maintenance command. Since SET is trying to write new data, Redis wants to ensure the expiration logic makes sense before it commits the value to memory. If the time is invalid, it rejects the whole command. The data isn't saved, and your application likely throws an unhandled exception.

Solution 1: Sanitize Your TTL Logic

The most immediate fix is to ensure your TTL is always at least one second (or one millisecond for PX). If your calculation results in zero or less, the data is technically already expired. In that case, you should either skip the SET entirely or delete any existing key to maintain consistency.

Python (redis-py) Implementation

def safe_set(key, value, ttl_seconds):
    # Ensure TTL is at least 1, or skip the write
    if ttl_seconds > 0:
        redis_client.set(key, value, ex=int(ttl_seconds))
    else:
        # The data is already stale; just ensure it's gone
        redis_client.delete(key)

Node.js (ioredis) Implementation

async function setWithExpiry(key, value, ttlMs) {
    if (ttlMs > 0) {
        await redis.set(key, value, "PX", ttlMs);
    } else {
        // Handle the edge case where the session expired during processing
        await redis.del(key);
    }
}

Solution 2: Use Absolute Expiration (EXAT/PXAT)

If you are running Redis 6.2 or newer, you can avoid TTL math altogether by using EXAT or PXAT. These options let you provide a specific Unix timestamp (e.g., 1735689600) rather than a relative offset. This is much more reliable for session management.

# Set key to expire at a specific timestamp
redis> SET session:user:123 "data" EXAT 1735689600

Pro Tip: Unlike EX, if you provide a timestamp in the past to EXAT, Redis 6.2+ will return OK but will either delete the existing key or never create the new one. This behavior is much safer for dynamic applications because it prevents the "invalid expire time" crash entirely.

Verifying the Fix

Test your implementation against these three scenarios to ensure stability:

  • Normal Case: Provide a TTL of 60 seconds. Verify the key exists and TTL key returns approximately 60.
  • The Edge Case: Force a TTL of 0 or -1 in your code. Ensure the application doesn't crash and the key is not present in Redis.
  • Clock Sync: If your app and Redis run on different servers, use NTP to keep clocks in sync. A 1-second drift can cause havoc when sessions are near their limit.

The Bottom Line

When you see ERR invalid expire time, it’s almost always a sign of a race condition in your TTL math. Redis SET expects a positive number. By adding a simple if ttl > 0 check or switching to EXAT, you can make your caching layer much more resilient to the "last second" session bug.

Related Error Notes