MySQL error 2006 appears in client logs and application output as ERROR 2006 (HY000): MySQL server has gone away. The SQLSTATE is HY000 (general error), and the C API condition name is CR_SERVER_GONE_ERROR. It means the TCP connection between your client and the MySQL server was severed before the query completed — or before a new query could be sent on a connection that had already gone idle.
What This Error Means
Unlike most MySQL errors that originate inside the server and are returned as a normal error response, error 2006 is a client-side error raised by the MySQL client library (libmysqlclient or the equivalent connector). The client attempted to write to or read from the network socket and received an error — either because the connection was cleanly closed by the server, or because the underlying TCP connection was reset or timed out at the network level.
From the application's perspective the connection handle is now invalid. Any transaction that was in progress has been rolled back by the server (MySQL rolls back uncommitted transactions when a client disconnects), but the client has no way to know how much of any multi-statement batch was executed before the drop. Retrying blindly can therefore cause duplicate writes or partial updates, which makes CR_SERVER_GONE_ERROR particularly dangerous for write-heavy workloads.
The companion error ERROR 2013 (HY000): Lost connection to MySQL server during query describes the same class of problem but occurs when the connection is lost partway through an active query rather than before the query is sent. The two often share the same root cause.
Common Causes
Idle connection timeout (
wait_timeout/interactive_timeout): MySQL closes connections that have been idle longer thanwait_timeoutseconds (default: 28800, i.e., 8 hours). Connection pools that hold connections open across this threshold will find them dead the next time they try to reuse one.Packet too large (
max_allowed_packet): MySQL silently drops the connection when a client tries to send a packet — a single query, a BLOB, a batch INSERT — larger thanmax_allowed_packet(default: 64 MB in MySQL 8.0, 4 MB in older versions). The server closes the connection without returning an error response, so the client sees a 2006.Server restart or crash: Any in-flight or pooled connection becomes invalid after
mysqldrestarts. Connections that were idle in a pool will fail with 2006 on the next use.Network-level connection drops: Firewalls, load balancers, NAT gateways, and cloud security groups often have idle TCP timeout settings shorter than MySQL's
wait_timeout. A connection that MySQL considers live may have been silently dropped by intermediate network equipment.Server-side resource exhaustion: If
mysqldruns out of memory, file descriptors, or threads, it may forcibly close client connections. Check the MySQL error log (/var/log/mysql/error.log) for[ERROR]lines around the time of the 2006.net_read_timeout/net_write_timeoutexceeded: These control how long the server waits for the client to finish sending a packet or for the client to read the server's response. A very slow network or a blocked application thread can trigger these.
How to Fix CR_SERVER_GONE_ERROR
Increase
wait_timeout/interactive_timeoutor reduce pool idle timeThe cleanest solution is to keep the pool's maximum idle time shorter than the server's
wait_timeout, rather than chasing the server setting upward.-- Check current values SHOW VARIABLES LIKE 'wait_timeout'; SHOW VARIABLES LIKE 'interactive_timeout'; -- Increase if appropriate (set in my.cnf for persistence) SET GLOBAL wait_timeout = 3600;In your connection pool (e.g., HikariCP, SQLAlchemy, PgBouncer-style proxies):
- Set
connection_max_age/pool_recycleto a value less thanwait_timeout(e.g., 3600 seconds ifwait_timeoutis 7200). - Enable connection validation /
testOnBorrowso the pool checks the connection before handing it to the application.
- Set
Increase
max_allowed_packetfor large payloads-- Check current value SHOW VARIABLES LIKE 'max_allowed_packet'; -- Increase (requires SUPER privilege; persist in my.cnf) SET GLOBAL max_allowed_packet = 128 * 1024 * 1024; -- 128 MBIn
my.cnf/my.ini:[mysqld] max_allowed_packet = 128MAlso set the same value under
[mysql](client section) if you are importing large dumps with themysqlCLI.Enable automatic reconnect in the connector (with caution)
Most connectors support an
autoReconnectoption. Enable it only for read workloads or truly idempotent writes — automatic reconnect after a dropped connection can silently lose a partially-executed transaction.Python (mysql-connector-python):
conn = mysql.connector.connect( host="localhost", user="app", password="...", database="mydb", connection_timeout=30, pool_reset_session=True, )JDBC URL:
jdbc:mysql://host/db?autoReconnect=true&failOverReadOnly=falseSend keepalives to defeat NAT/firewall timeouts
Enable TCP keepalive at the OS level or configure your connector to send periodic
SELECT 1pings:# my.cnf — makes the server send TCP keepalives [mysqld] tcp_keepalive_time = 120Alternatively, configure your connection pool to run a validation query (
SELECT 1) before each checkout.Check the MySQL error log after a server-side drop
# Tail the MySQL error log for context around the disconnection sudo tail -n 200 /var/log/mysql/error.log | grep -E 'ERROR|Warning|Got an error'Handle 2006 explicitly in application code
For long-running or batch jobs, catch
OperationalError(Python),CommunicationsException(Java), or equivalent, and implement retry logic with exponential backoff. Always re-fetch the connection from the pool rather than reusing the dead handle.import mysql.connector from mysql.connector import errors import time def execute_with_retry(pool, sql, params=None, max_retries=3): for attempt in range(max_retries): try: conn = pool.get_connection() cursor = conn.cursor() cursor.execute(sql, params) conn.commit() return cursor.fetchall() except errors.OperationalError as e: if e.errno == 2006 and attempt < max_retries - 1: time.sleep(2 ** attempt) continue raise finally: try: conn.close() except Exception: pass
Additional Information
- Error 2006 is defined in the MySQL C API (
errmsg.h) and is raised entirely on the client side. It does not appear in the MySQL server's own error log unless the drop is caused by a server-side action (crash, OOM kill, explicitKILL CONNECTION). - Related client errors: 2013 (
CR_SERVER_LOST) — connection lost mid-query; 2003 (CR_CONN_HOST_ERROR) — cannot connect at all; 2055 (CR_SERVER_LOST_EXTENDED) — detailed version of 2013. - MySQL 8.0 raised the default
max_allowed_packetfrom 4 MB (5.7) to 64 MB. If you are migrating from 5.7 and were previously working around this limit with application chunking, verify your new default does not mask over-large payloads. - ORMs: Django raises
django.db.utils.OperationalError: (2006, 'MySQL server has gone away'); SQLAlchemy raisessqlalchemy.exc.OperationalError. Both wrap the underlying connector error. Django'sCONN_MAX_AGEsetting must be set belowwait_timeoutto avoid stale connections. - ProxySQL and MySQL Router both expose their own idle connection timeouts that can cause 2006 independently of the backend server's
wait_timeout. Checkmysql-connection_max_age_msin ProxySQL if you use it.
Frequently Asked Questions
Why does this error only happen after the application has been idle for a while?
MySQL's wait_timeout (default 8 hours) closes idle connections server-side. If your connection pool holds connections open without validating them, those connections look alive to the pool but are dead to MySQL. The first query after the idle period hits the closed socket and raises 2006. Setting pool_recycle to a value shorter than wait_timeout, or enabling connection validation on checkout, eliminates this class of occurrence.
Can I safely retry a query that failed with error 2006?
It depends on the query. A pure SELECT is always safe to retry. An INSERT, UPDATE, or DELETE is only safe to retry if the statement is idempotent (e.g., INSERT ... ON DUPLICATE KEY UPDATE, or an UPDATE with a deterministic WHERE clause). Never blindly retry a multi-statement transaction — you cannot know which statements ran before the connection dropped.
Is error 2006 the same as error 2013?
They are closely related but distinct. Error 2006 (CR_SERVER_GONE_ERROR) is raised when the client attempts to send a new query on a connection that is already closed. Error 2013 (CR_SERVER_LOST) is raised when the connection is lost while a query is actively being transmitted or its response is being received. The fixes overlap substantially: both are caused by timeouts, oversized packets, and network drops.
How do I tell if max_allowed_packet is the cause?
If the error occurs specifically when sending large payloads (bulk inserts, large BLOBs, long IN(...) lists), run SHOW VARIABLES LIKE 'max_allowed_packet' and compare the result with the size of your payload. Also check the MySQL error log for lines containing Got a packet bigger than 'max_allowed_packet' — that message appears on the server side just before the connection is dropped.