How to Fix MySQL Error 2006: MySQL Server Has Gone Away

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

  1. Idle connection timeout (wait_timeout / interactive_timeout): MySQL closes connections that have been idle longer than wait_timeout seconds (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.

  2. 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 than max_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.

  3. Server restart or crash: Any in-flight or pooled connection becomes invalid after mysqld restarts. Connections that were idle in a pool will fail with 2006 on the next use.

  4. 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.

  5. Server-side resource exhaustion: If mysqld runs 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.

  6. net_read_timeout / net_write_timeout exceeded: 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

  1. Increase wait_timeout / interactive_timeout or reduce pool idle time

    The 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_recycle to a value less than wait_timeout (e.g., 3600 seconds if wait_timeout is 7200).
    • Enable connection validation / testOnBorrow so the pool checks the connection before handing it to the application.
  2. Increase max_allowed_packet for 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 MB
    

    In my.cnf / my.ini:

    [mysqld]
    max_allowed_packet = 128M
    

    Also set the same value under [mysql] (client section) if you are importing large dumps with the mysql CLI.

  3. Enable automatic reconnect in the connector (with caution)

    Most connectors support an autoReconnect option. 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=false
    
  4. Send keepalives to defeat NAT/firewall timeouts

    Enable TCP keepalive at the OS level or configure your connector to send periodic SELECT 1 pings:

    # my.cnf — makes the server send TCP keepalives
    [mysqld]
    tcp_keepalive_time = 120
    

    Alternatively, configure your connection pool to run a validation query (SELECT 1) before each checkout.

  5. 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'
    
  6. 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, explicit KILL 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_packet from 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 raises sqlalchemy.exc.OperationalError. Both wrap the underlying connector error. Django's CONN_MAX_AGE setting must be set below wait_timeout to 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. Check mysql-connection_max_age_ms in 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.

Subscribe to the Pulse Newsletter

Get early access to new Pulse features, insightful blogs & exclusive events , webinars, and workshops.

We use cookies to provide an optimized user experience and understand our traffic. To learn more, read our use of cookies; otherwise, please choose 'Accept Cookies' to continue using our website.