ERROR 2006 (HY000): MySQL server has gone away (client error code CR_SERVER_GONE_ERROR) is a client-side error indicating that the TCP connection to the MySQL server was closed before the client expected it. The server did not refuse the connection — it already accepted it and then either timed it out, the server restarted, or the network dropped it.
Impact
The error surfaces as a database query failure from the application's perspective. Connection pools that do not validate connections before reuse return this error on the first query after the connection goes stale. This causes a class of "works fine, then fails after idle" bugs that are especially common in long-running processes (workers, cron jobs, scheduled tasks) that acquire a database connection once and reuse it across multiple cycles.
Common Causes
wait_timeoutorinteractive_timeoutexpired: MySQL closed an idle connection after the configured idle time (default 8 hours, but many managed databases use 60–600 s)- Query or transaction took longer than
net_read_timeoutornet_write_timeout(default 30 s each) — MySQL closes connections that stall mid-transfer - A single packet exceeded
max_allowed_packet— MySQL closes the connection after rejecting an oversized packet (see also: error 1153) - MySQL server restarted, crashed, or ran out of memory and OOM-killed the mysqld process
- Network infrastructure (firewall, NAT gateway, load balancer) silently dropped the idle TCP connection
- Connection pool returned a connection to the pool that was already closed server-side, then handed it out again without validation
- Long-running
mysqldumpor import that the server killed due to timeout or resource limits - Replication: replica applies a transaction that was larger than its
max_allowed_packet - Underlying OS-level keepalive not configured to detect dead TCP connections before MySQL's own timeout fires
Troubleshooting and Resolution Steps
Check the timeout settings:
SHOW VARIABLES LIKE 'wait_timeout'; SHOW VARIABLES LIKE 'interactive_timeout'; SHOW VARIABLES LIKE 'net_read_timeout'; SHOW VARIABLES LIKE 'net_write_timeout'; SHOW VARIABLES LIKE 'max_allowed_packet';wait_timeoutgoverns non-interactive connections (typical application pools).interactive_timeoutgoverns connections opened with theCLIENT_INTERACTIVEflag (themysqlCLI).Confirm whether the server restarted recently:
SHOW GLOBAL STATUS LIKE 'Uptime'; -- or SELECT * FROM performance_schema.global_status WHERE VARIABLE_NAME = 'Uptime';A low
Uptimevalue relative to when the error started indicates a crash or restart.Check the MySQL error log for OOM kills, crashes, or assertion failures:
SHOW VARIABLES LIKE 'log_error';Then inspect that file path on the server. Look for
[ERROR],[Warning] Aborted connection, orKilled.Count aborted connections — a rising number indicates the problem is systematic:
SHOW GLOBAL STATUS LIKE 'Aborted_connects'; SHOW GLOBAL STATUS LIKE 'Aborted_clients';Aborted_clientsincrements when the client disconnects without a properCOM_QUIT; this is the typical signature of a pool returning a timed-out connection.Configure the connection pool to validate connections before use. For HikariCP (Java):
connectionTestQuery=SELECT 1 # or rely on JDBC4 isValid() — preferred keepaliveTime=30000 # ms — send a ping to keep the connection alive idleTimeout=60000 # ms — remove connections idle longer than this maxLifetime=1800000 # ms — recycle all connections every 30 minFor SQLAlchemy (Python):
engine = create_engine( DATABASE_URL, pool_pre_ping=True, # validates connection before checkout pool_recycle=1800, # recycle connections every 30 min pool_timeout=30, )pool_pre_ping=Trueissues aSELECT 1before handing out each connection and discards it on failure, transparently reconnecting.Lower
wait_timeoutserver-side so connections are reclaimed faster and pools are forced to reconnect on their schedule rather than accumulating stale handles:SET GLOBAL wait_timeout = 300; -- 5 minutes SET PERSIST wait_timeout = 300;Match this to a value shorter than your pool's
maxLifetimeso the pool recycles before the server times out.Raise
net_read_timeoutfor long-running imports or bulk operations:SET SESSION net_read_timeout = 300; SET SESSION net_write_timeout = 300;Use session-level changes, not global, so regular OLTP queries are not affected.
Enable TCP keepalive at the OS level to detect dead connections across NAT gateways:
# Linux — adjust in /etc/sysctl.conf for persistence sysctl -w net.ipv4.tcp_keepalive_time=60 sysctl -w net.ipv4.tcp_keepalive_intvl=10 sysctl -w net.ipv4.tcp_keepalive_probes=3This makes the OS send keepalive probes after 60 s of idle, so stateful firewalls with a 5-minute idle timeout will see traffic and not drop the connection. MySQL's own
wait_timeoutfires independently.Increase
max_allowed_packetif the error occurs on large queries or imports:SET GLOBAL max_allowed_packet = 268435456; -- 256 MBCheck whether the error correlates with requests that send or receive large payloads.
For worker processes and scheduled jobs, explicitly re-establish the connection at the start of each work unit rather than assuming the connection held between cycles is still alive:
# Django example — close old connections before a long task from django.db import connection def run_batch(): connection.close() # force reconnect on next query # ... now run queriesIn Celery, use
django.db.close_old_connections()in atask_prerunsignal handler.Use
--reconnectin the mysql CLI for interactive sessions that may be idle:mysql --reconnect -u root -p mydb
Additional Information
- Error 2006 (CR_SERVER_GONE_ERROR) is a client-side code defined in
errmsg.h; the server never sees it. Error 2013 (CR_SERVER_LOST) is similar — the server connection was lost mid-query rather than before the query started. Both have the same root causes and fixes. - Amazon RDS, Aurora, and Google Cloud SQL all default
wait_timeoutto much lower values than the MySQL default (often 60–600 s for serverless variants). Applications written against a local MySQL with the 8-hour default will break immediately when deployed to a managed database. - The
pool_pre_ping/connectionTestQueryoption adds one RTT to each connection checkout. For high-throughput OLTP, consider a longerkeepaliveTime(to send periodic pings) combined with a shortermaxLifetime(to recycle) instead of a pre-ping on every checkout. - Galera Cluster nodes that fail a quorum check close all client connections — this looks identical to a timeout from the client's perspective.
Frequently Asked Questions
Q: The error only happens at night or on weekends. Why?
A: Those are periods of low traffic when application connections are idle long enough to hit wait_timeout. The fix is either a shorter pool maxLifetime that recycles before the server times out, or pool_pre_ping=True.
Q: I set pool_pre_ping=True and still get the error occasionally. Why?
A: pool_pre_ping checks the connection at checkout time. If the server closes the connection after the ping but before the actual query executes — possible under very short wait_timeout values or flapping networks — the error can still occur. Retry logic for 2006 is still necessary.
Q: The error occurs during a mysqldump. How do I fix it?
A: Set --net-read-timeout and --net-write-timeout on the dump command, or use --single-transaction for InnoDB tables (which starts a transaction and uses snapshot reads, avoiding long per-table lock holds). Also check that max_allowed_packet is large enough for your biggest table row.
Q: Can the error happen if MySQL is still running?
A: Yes. The most common non-restart cause is wait_timeout expiring on an idle connection held by the pool.
Q: How do I distinguish a timeout from a crash?
A: Check SHOW GLOBAL STATUS LIKE 'Uptime'. If uptime reset recently, it was a restart. Also check Aborted_clients — if it's increasing steadily, that's idle timeouts. Check the error log for crash signatures (core dump path, assertion failure, signal 6/11).