Home / Databases / MySQL

DatabasesTroubleshootingIntermediate

MySQL 'server has gone away' (error 2006) and 'Lost connection during query' (error 2013): find the cause and fix it

Work out whether an idle timeout, max_allowed_packet, a network timeout, a proxy or a server restart closed the connection, then fix the server setting or the client pool.

Deepak Bhardwaj7 min read

Applies toMySQL 8.0 and 8.4 LTS, Amazon RDS/Aurora MySQL, Azure Database for MySQL Flexible Server

The symptom

The application log shows one of these, often at random and with no obvious pattern:

ERROR 2006 (HY000): MySQL server has gone away
ERROR 2013 (HY000): Lost connection to MySQL server during query
ERROR 4031 (HY000): The client was disconnected by the server because of inactivity.

All three mean the same thing at the TCP level: the connection the client was using is no longer there. The error number tells you when the client noticed, not why it happened:

  • 2006 - the client tried to send a statement on a connection that was already closed.
  • 2013 - the connection dropped while a statement was running or results were streaming back.
  • 4031 - newer servers (8.0.24 and later) tell the client it was closed for inactivity, which points straight at wait_timeout.

Your job is to find which of five usual causes closed it.

Quick map: symptom to cause

What you see Likely cause Fix
2006 or 4031 after the app sat idle wait_timeout closed an idle pooled connection Pool maxLifetime / recycle shorter than wait_timeout, validate on borrow
2006/2013 on a large INSERT, BLOB or import Packet bigger than max_allowed_packet Raise it on server and client
2013 on a long result set or slow client net_write_timeout / net_read_timeout expired Raise for that session, or stream faster
Drops after a fixed idle time, server shows nothing Firewall, NAT or load balancer idle timeout TCP keepalive, shorter pool lifetime
Every client drops at the same moment Server restarted, crashed or failed over Check Uptime, error log, OOM killer
One session drops mid-query Someone or a tool ran KILL Check audit/tooling (pt-kill, scripts)
2013 "at 'reading initial communication packet'" Connect stage: connect_timeout, DNS, firewall See the connect section below

Check 1: did the server restart?

Rule this out first, because it changes everything else.

SHOW GLOBAL STATUS LIKE 'Uptime';
SELECT NOW() - INTERVAL VARIABLE_VALUE SECOND AS started_at
FROM performance_schema.global_status WHERE VARIABLE_NAME = 'Uptime';

If started_at matches the error time, read the error log and the kernel log:

sudo tail -200 /var/log/mysql/error.log      # path varies; check log_error
sudo dmesg -T | grep -iE 'killed process|out of memory'

An OOM kill of mysqld is a memory sizing problem, not a connection problem. On RDS, check the instance events (aws rds describe-events --source-identifier mydb --source-type db-instance --duration 1440); on Azure, check the server's Activity log and Resource Health.

Check 2: read the aborted-connection notes

The server records why it dropped a connection, but only at log_error_verbosity = 3 (notes). Turn it on temporarily:

SELECT @@log_error_verbosity;
SET GLOBAL log_error_verbosity = 3;

Then look for lines like:

[Note] Aborted connection 1234 to db: 'shop' user: 'app' host: '10.20.4.17' (Got timeout reading communication packets).
[Note] Aborted connection 1235 to db: 'shop' user: 'app' host: '10.20.4.17' (Got a packet bigger than 'max_allowed_packet' bytes).
[Note] Aborted connection 1236 to db: 'shop' user: 'app' host: '10.20.4.17' (Got an error reading communication packets).
Log reason Meaning
Got timeout reading communication packets Idle past wait_timeout, or net_read_timeout mid-statement
Got a packet bigger than 'max_allowed_packet' bytes Packet limit
Got an error reading communication packets Client or something in between closed the socket without a clean QUIT

The counters tell you the rate:

SHOW GLOBAL STATUS WHERE Variable_name IN
 ('Aborted_clients','Aborted_connects','Connection_errors_max_connections',
  'Connection_errors_internal','Connection_errors_peer_address');

Aborted_clients rising steadily is the classic idle-timeout or "app never closes connections" pattern. Set log_error_verbosity back to 2 when you are done if notes are too noisy.

Check 3: the timeout settings

SELECT @@global.wait_timeout, @@global.interactive_timeout,
       @@global.net_read_timeout, @@global.net_write_timeout,
       @@global.connect_timeout, @@global.max_allowed_packet;

Then check what the application's sessions actually got. Some drivers and frameworks set wait_timeout per session:

SELECT t.PROCESSLIST_ID, t.PROCESSLIST_USER, v.VARIABLE_VALUE AS session_wait_timeout
FROM performance_schema.threads t
JOIN performance_schema.variables_by_thread v ON v.THREAD_ID = t.THREAD_ID
WHERE v.VARIABLE_NAME = 'wait_timeout' AND t.PROCESSLIST_USER = 'app';

Fix: idle connections closed by wait_timeout

This is the most common cause. A connection pool keeps connections open for hours; the server closes any connection idle longer than wait_timeout; the next borrower gets 2006 or 4031.

Do not just set wait_timeout to a huge value. Abandoned connections then pile up until you hit max_connections. Fix the pool instead:

  • HikariCP (Java): maxLifetime a minute or more below the lowest timeout in the path (wait_timeout, proxy and firewall idle timeouts). Set keepaliveTime if a network device drops idle flows.
  • SQLAlchemy (Python): pool_pre_ping=True and pool_recycle below wait_timeout.
from sqlalchemy import create_engine

engine = create_engine(
    "mysql+pymysql://app:***@db.internal/shop",
    pool_pre_ping=True,   # test the connection before handing it out
    pool_recycle=1800,    # seconds; keep below wait_timeout
)
  • Long batch jobs that sleep between steps: reconnect on 2006, or set a longer wait_timeout for that session only:
SET SESSION wait_timeout = 7200;

Fix: max_allowed_packet

A single statement or row bigger than the limit closes the connection. The server and the client each have their own limit; the smaller one wins.

-- Self-managed, survives restart (8.0+)
SET PERSIST max_allowed_packet = 268435456;   -- 256 MB

New connections pick up the global value; existing pooled connections keep the old one until they reconnect.

For managed services, change the parameter rather than using SET PERSIST:

# Amazon RDS / Aurora MySQL (custom parameter group required)
aws rds modify-db-parameter-group \
  --db-parameter-group-name mysql84-app \
  --parameters "ParameterName=max_allowed_packet,ParameterValue=268435456,ApplyMethod=immediate"

# Azure Database for MySQL Flexible Server
az mysql flexible-server parameter set \
  --resource-group rg-db --server-name mysql-prod-01 \
  --name max_allowed_packet --value 268435456

And raise the client side for imports and dumps:

mysql --max_allowed_packet=256M -h db.internal -u app -p shop < big_import.sql
mysqldump --max_allowed_packet=256M --single-transaction shop > shop.sql

Warning

max_allowed_packet is a per-connection buffer ceiling. Raising it to the maximum on a server with thousands of connections increases worst-case memory use. Raise it to what the largest real row or statement needs, and prefer batching huge multi-row INSERTs into smaller chunks.

Fix: network timeouts during long transfers

When a client reads a large result set slowly (for example, processing each row before fetching the next), the server can hit net_write_timeout while waiting to send. When a client uploads slowly, the server can hit net_read_timeout. Raise them for the session that needs it, not globally:

SET SESSION net_write_timeout = 600;
SET SESSION net_read_timeout  = 600;

Fix: proxies, load balancers and firewalls

If the server log shows Got an error reading communication packets (or nothing) and the drops happen after a consistent idle period, something in the network path is timing out idle TCP flows. Check the idle timeout of your load balancer, NAT gateway, firewall or ProxySQL/MySQL Router, then make the pool lifetime shorter than the smallest of them. Enabling TCP keepalive in the driver also helps; check your driver's option name.

Connect-stage 2013

Lost connection to MySQL server at 'reading initial communication packet' happens before authentication. Check reachability and the host cache:

nc -vz db.internal 3306
SELECT IP, HOST, SUM_CONNECT_ERRORS, COUNT_HANDSHAKE_ERRORS
FROM performance_schema.host_cache ORDER BY SUM_CONNECT_ERRORS DESC LIMIT 10;

Slow reverse DNS can push the handshake past connect_timeout; skip_name_resolve = ON removes that dependency (accounts must then use IP addresses or %, not host names).

Prevent it next time

  • Set pool lifetime below wait_timeout and every network idle timeout, and validate connections on borrow. Write the numbers down in the service runbook.
  • Alert on the rate of Aborted_clients and Aborted_connects, not the absolute value.
  • Alert on Uptime resetting, so restarts are never mistaken for a "network blip".
  • Size max_allowed_packet from the largest real row or BLOB, and test bulk loads against the same setting production uses.
  • Keep application retry logic for 2006/2013 idempotent: a 2013 during a COMMIT means you do not know whether it committed.