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
EVALcall - 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 --latencyor 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
SCANcalls instead. - Watch the slow log in production. Alert if any
EVALexceeds 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-writesflag, which letsSCRIPT KILLwork even after execution starts โ removing the UNKILLABLE scenario entirely.

