Skip to content
DevOps AI ToolKit
Newsletter
All guides
AI for Redis By James Joyner IV · · 8 min read Last reviewed Jul 2026

Redis Error Guide: 'EXEC without MULTI' — Fix Broken Transaction Sequencing and Pooled Connections

Quick answer

Fix ERR EXEC without MULTI in Redis: understand MULTI/EXEC transaction state and how connection pooling and reconnects desync transaction commands.

  • #redis
  • #database
  • #troubleshooting
  • #errors
Free toolkit

Stuck on this Redis error? Get the free incident triage checklist

A one-page PDF — the exact steps to isolate, fix, and verify a production error like this one. No spam, unsubscribe anytime.

Overview

Redis returns EXEC without MULTI when it receives an EXEC on a connection that is not currently inside a transaction — that is, no matching MULTI opened a transaction on that same connection first. Redis transactions are strictly per-connection and stateful: MULTI opens a queue, subsequent commands are queued, and EXEC runs them. Sending EXEC when no queue is open is a protocol/state error.

The literal error clients receive:

(error) ERR EXEC without MULTI

This is almost never a Redis bug; it is a sequencing or connection-management bug in the client. The usual causes are a MULTI that was never sent (or was rejected), a transaction already ended by a prior EXEC/DISCARD, or — most insidiously — a connection pool that sent MULTI on one connection and EXEC on a different one.

Symptoms

  • EXEC fails with EXEC without MULTI, often intermittently under concurrency.
  • The error correlates with connection-pool reuse, reconnects, or pipelining.
  • A prior command in the intended transaction errored, and the client sent EXEC anyway.
redis-cli EXEC
(error) ERR EXEC without MULTI
# Correct sequence on one connection
redis-cli -3 <<'EOF'
MULTI
SET a 1
INCR a
EXEC
EOF

Common Root Causes

1. EXEC sent on a different pooled connection than MULTI

The client library checked out one connection for MULTI, then a different connection from the pool for EXEC. The second connection never saw MULTI.

2. MULTI was never sent or was rejected

The MULTI command was skipped by a code path, or rejected (e.g. MULTI nested / connection reset), so EXEC arrives with no open transaction.

3. The transaction already ended

A prior EXEC or DISCARD already closed the transaction, and a duplicate EXEC followed.

4. Reconnect mid-transaction

The connection dropped and reconnected between MULTI and EXEC; the new socket has no transaction state, so EXEC fails.

5. Manual pipelining that reorders commands

Hand-built pipelines that interleave commands from different logical operations can deliver EXEC before its MULTI.

Diagnostic Workflow

Step 1: Reproduce the correct sequence on one connection

redis-cli
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> SET a 1
QUEUED
127.0.0.1:6379> EXEC
1) OK

If this works but your app fails, the bug is in how the app manages the connection, not in Redis.

Step 2: Confirm MULTI and EXEC share one connection

Inspect the client library’s transaction/pool usage. The MULTI, all queued commands, and EXEC must run on the same physical connection, held for the transaction’s duration.

Step 3: Watch the actual command stream

# Briefly, on a non-prod or low-traffic instance only
redis-cli MONITOR | grep -iE 'MULTI|EXEC|DISCARD'

Look for an EXEC with no preceding MULTI on the same client, or MULTI/EXEC from different client addresses/ports.

Step 4: Check for reconnects and errors between MULTI and EXEC

redis-cli INFO stats | grep -E 'total_connections_received|rejected_connections'
sudo journalctl -u redis-server --no-pager | grep -iE 'MULTI|EXEC|closing' | tail

Step 5: Prefer a transaction/Lua abstraction

Confirm whether the code hand-writes MULTI/EXEC or uses the library’s transaction helper (pipeline-with-transaction), which pins the connection for you.

Example Root Cause Analysis

A payment service intermittently logs ERR EXEC without MULTI under load. It uses a connection pool and hand-writes transactions: it calls client.execute("MULTI"), queues writes, then client.execute("EXEC") — but each execute checks out a fresh connection from the pool.

MONITOR on a canary shows the smoking gun: MULTI and EXEC arrive from different client ports:

1720000000.1 [0 10.0.0.5:51122] "MULTI"
1720000000.2 [0 10.0.0.5:51122] "SET" "txn:9" "pending"
1720000000.3 [0 10.0.0.5:51188] "EXEC"     <-- different port!

Under low load the pool happened to hand back the same connection, so it worked; under concurrency it handed out a different one, and the second connection had no open MULTI. The fix was to use the library’s transaction/pipeline abstraction that pins one connection for the whole transaction, rather than issuing MULTI and EXEC as independent pool checkouts:

# pseudocode — pin one connection for the transaction
with pool.pipeline(transaction=True) as tx:
    tx.multi()
    tx.set("txn:9", "pending")
    tx.incr("txn:count")
    tx.execute()          # MULTI...EXEC all on one connection

After switching to the pinned-connection transaction API the error disappeared entirely, because MULTI, the queued commands, and EXEC were guaranteed to travel on the same socket.

Prevention Best Practices

  • Always run MULTI, the queued commands, and EXEC on the same connection; use your client library’s transaction/pipeline helper rather than issuing them as separate pool checkouts.
  • Never let a connection return to the pool while a transaction is open; pin it for the transaction’s lifetime.
  • Handle reconnects by abandoning and restarting the transaction — transaction state does not survive a new socket.
  • For complex atomic logic, prefer a Lua script or a Redis Function, which is atomic without cross-command connection state.
  • Check for and handle errors on the queued commands before calling EXEC (a bad queued command aborts the transaction with EXECABORT).
  • Feed the command stream into the free incident assistant, and browse more Redis guides.

Quick Command Reference

# Correct, single-connection transaction
redis-cli <<'EOF'
MULTI
SET a 1
INCR a
EXEC
EOF

# Observe MULTI/EXEC pairing and client ports (low-traffic only)
redis-cli MONITOR | grep -iE 'MULTI|EXEC|DISCARD'

# Reconnect / rejection signals
redis-cli INFO stats | grep -E 'total_connections_received|rejected_connections'

# Abandon a transaction cleanly
redis-cli DISCARD

Conclusion

EXEC without MULTI means Redis received an EXEC on a connection with no open transaction. The typical root causes are:

  1. EXEC sent on a different pooled connection than MULTI.
  2. MULTI never sent or rejected.
  3. A transaction already closed by a prior EXEC/DISCARD.
  4. A reconnect between MULTI and EXEC that reset transaction state.
  5. Manual pipelining that reordered the commands.

Because transactions are per-connection state, the durable fix is to pin one connection for the whole MULTIEXEC sequence via your client’s transaction helper — or move the atomic logic into a Lua script/Function, which needs no cross-command connection state at all.

Free download · 368-page PDF

Fixed it? Get 500 Redis & DevOps AI prompts — free

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.

Did this fix your issue?

Free download · 368-page PDF

Get 500 Battle-Tested DevOps AI Prompts — Free

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.