How to Fix Redis 'BUSY Redis is busy running a script' Error

intermediate๐Ÿ”ด Redis2026-04-16| Redis 2.6+, any OS (Linux, macOS, Windows WSL), any Redis client (redis-cli, Python redis-py, Node ioredis, etc.)

Error Message

(error) BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.
#redis#lua#script#performance#busy#blocking

The Error

You ran a command against Redis and got this back:

(error) BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.

Redis is stuck executing a Lua script. Until that script finishes โ€” or gets killed โ€” every other command you send gets rejected. Redis is single-threaded: one runaway script holds the entire server hostage.

Why This Happens

Redis executes Lua scripts atomically via EVAL or EVALSHA. That atomicity is the whole point โ€” no other command can interrupt mid-script. The trade-off is brutal: a script that runs too long freezes your entire Redis instance.

The default timeout is 5000ms (lua-time-limit). Cross that threshold and Redis stops accepting everything except SCRIPT KILL and SHUTDOWN NOSAVE. Common ways scripts blow past it:

  • An infinite loop or unbounded iteration โ€” e.g., scanning 10 million keys inside a single EVAL call
  • A redis.call() loop with no exit condition
  • A script that scanned 500 keys fine in dev but hits 5 million in production
  • A race condition where the loop's terminator (a cursor reaching 0) never arrives

Step-by-Step Fix

Step 1: Kill the Running Script

Open a new terminal โ€” your current connection is probably hanging. Connect to Redis:

redis-cli -h 127.0.0.1 -p 6379

Then run:

SCRIPT KILL

If the script hasn't written anything yet, Redis terminates it immediately:

OK

Done. Your server is unblocked and accepting normal commands again.

Step 2: If SCRIPT KILL Returns an Error

Hit this instead?

(error) UNKILLABLE Sorry the script already executed write commands against the dataset. You can either wait it to finish or kill the server in a non destructive way.

Redis is protecting you from a half-written dataset. A script that already performed writes can't be safely aborted mid-execution. Two paths forward:

  • Wait it out โ€” if the script is making progress and will eventually finish, let it. Track status with redis-cli --latency or tail the Redis log.
  • Restart Redis โ€” if it's truly stuck in an infinite loop, a restart is your only option. Pick the right method:
# Linux systemd
sudo systemctl restart redis

# macOS Homebrew
brew services restart redis

# Direct process
sudo service redis-server restart

About SHUTDOWN NOSAVE: this kills the server and discards everything written since the last RDB/AOF save. Only use it if you're certain those in-memory changes are safe to lose.

Step 3: Find the Problematic Script

Once the server is responsive, find out what triggered the BUSY state. Pull the slow log:

redis-cli SLOWLOG GET 10

Scan the output for EVAL or EVALSHA entries. An execution time of 5,000,000 microseconds (5 seconds) or more is your culprit. Cross-reference with your application logs to find which code path triggered it.

Step 4: Fix the Script

Most BUSY errors share the same root cause: too much work crammed into a single Lua script. Here's how to fix the common patterns.

Break up unbounded scans:

-- BAD: iterates until cursor == 0, no upper bound
local cursor = 0
repeat
  local result = redis.call('SCAN', cursor, 'MATCH', 'prefix:*')
  cursor = tonumber(result[1])
  -- process keys...
until cursor == 0

-- BETTER: move the scan loop to your application layer entirely
-- Don't do full key scans inside a single EVAL call

Move heavy logic to application code:

# Python redis-py โ€” scan in batches of 100
import redis

r = redis.Redis()
cursor = 0
while True:
    cursor, keys = r.scan(cursor, match='prefix:*', count=100)
    for key in keys:
        # process key
        pass
    if cursor == 0:
        break

Lua scripts shine at one thing: atomic read-modify-write on a small, bounded set of keys. Push batch processing, scanning, and anything with variable iteration counts back into your application.

Step 5: Tune lua-time-limit (Optional)

The 5-second default gives scripts a reasonable window before Redis starts refusing commands. You can change it in redis.conf:

# redis.conf
lua-time-limit 5000   # milliseconds

# Or change it live without a restart:
redis-cli CONFIG SET lua-time-limit 5000

Raising this just delays the pain. Only bump it up if you have a specific, reviewed script with a known upper bound on execution time โ€” not as a quick fix for a loop you haven't audited.

Verify the Fix

Quick sanity check after killing the script or restarting the server:

redis-cli PING
# Expected: PONG

redis-cli INFO server | grep redis_version
# Should return version info without hanging

Then confirm reads and writes both work:

redis-cli SET test-key "hello"
# OK
redis-cli GET test-key
# "hello"
redis-cli DEL test-key

All three respond instantly? You're good.

Prevent It From Happening Again

  • Test with production-scale data. A script scanning 100 keys takes microseconds. The same script against 10 million keys will breach 5 seconds easily โ€” and you won't know until it's too late.
  • Set client-side timeouts so your app doesn't block indefinitely waiting for a stuck EVAL:
# Python redis-py
r = redis.Redis(socket_timeout=5.0)

# Node ioredis
const client = new Redis({ commandTimeout: 5000 });
  • No unbounded loops in Lua. If your iteration count depends on data size, handle it in application code with batched SCAN calls instead.
  • Watch the slow log in production. Alert if any EVAL exceeds 1โ€“2 seconds โ€” catching a slow script at 2 seconds beats dealing with a frozen server at 6.
  • On Redis 7.0+, consider FUNCTION over EVAL. Functions support the no-writes flag, which lets SCRIPT KILL work even after execution starts โ€” removing the UNKILLABLE scenario entirely.

Related Error Notes