MySQL Error 2013 occurs when the connection between client and server is unexpectedly terminated mid-query. Fix it by tuning timeouts, max_allowed_packet, optimizing slow queries, and adding retry logic.
MySQL Error 2013 indicates that an established connection was lost during query execution. The server or client terminates the connection before the query completes, usually because the query takes too long, the server becomes unresponsive, network issues occur, or data packets exceed the maximum allowed size. This is a connectivity error, not a syntax or logic error—the query itself may be valid, but the connection can't sustain it.
The default net_read_timeout and net_write_timeout are 30 seconds. For long queries or slow networks, increase these values:
-- Check current settings:
SHOW VARIABLES LIKE 'net_read_timeout';
SHOW VARIABLES LIKE 'net_write_timeout';
-- Set temporarily (until server restart):
SET GLOBAL net_read_timeout = 120;
SET GLOBAL net_write_timeout = 120;
-- Set permanently in my.cnf or my.ini:
[mysqld]
net_read_timeout = 120
net_write_timeout = 120Note: SET GLOBAL only affects new connections, not existing ones. To make permanent changes take effect, restart MySQL:
# Linux:
sudo systemctl restart mysql
# macOS:
brew services restart [email protected]Increase further (300+ seconds) if queries still time out.
These settings control how long a connection can remain idle before the server closes it:
-- Check current values:
SHOW VARIABLES LIKE 'wait_timeout';
SHOW VARIABLES LIKE 'interactive_timeout';
-- Set temporarily (affects new connections only):
SET GLOBAL wait_timeout = 600;
SET GLOBAL interactive_timeout = 600;
-- Set permanently in my.cnf:
[mysqld]
wait_timeout = 600
interactive_timeout = 600The default is 28800 seconds (8 hours), but it is often reduced by hosting providers or proxies. Increase to at least 600-3600 seconds if idle connections are being dropped.
If you're transferring large BLOB, TEXT, or JSON data, increase this limit on both the server and the client:
-- Check current setting:
SHOW VARIABLES LIKE 'max_allowed_packet';
-- Set temporarily on the server (affects new connections):
SET GLOBAL max_allowed_packet = 67108864; -- 64MB in bytesSet permanently in my.cnf (the suffix form is accepted in config files):
[mysqld]
max_allowed_packet = 64M
-- Also set on client side (mysqldump, mysql CLI):
[mysqldump]
max_allowed_packet = 64M
[mysql]
max_allowed_packet = 64MDefault is 4MB on the server. Increase to 16M, 32M, or 64M depending on your data size. A permanent server-side change requires a restart. Note that the effective limit for a transfer is the smaller of the client and server values, so both must be raised.
Long-running queries are the primary cause of ERROR 2013. Identify and optimize them:
-- Enable slow query log to find culprits:
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 5; -- Log queries taking >5 seconds
-- Check the log:
-- Location: /var/log/mysql/slow.log (Linux) or MySQL datadirOptimization tips:
- Add indexes to WHERE, JOIN, and ORDER BY columns
- Use EXPLAIN to analyze query execution plans
- Reduce result set size with LIMIT
- Break complex queries into smaller operations
- Avoid SELECT * and fetch only needed columns
- Use caching for expensive repeated aggregations
Example optimization:
-- SLOW (full table scan without index):
SELECT * FROM orders WHERE customer_id = 123;
-- FAST (with index, selecting only needed columns):
CREATE INDEX idx_customer_id ON orders(customer_id);
SELECT id, amount FROM orders WHERE customer_id = 123;Server overload (CPU, memory, disk) can cause connections to drop:
# Check system resources:
top
free -h
df -h
# Check MySQL process:
ps aux | grep mysqldMonitor MySQL:
-- Show active processes:
SHOW PROCESSLIST;
-- Kill a specific stuck query (use carefully):
KILL QUERY 12345; -- replace 12345 with the thread/connection id from SHOW PROCESSLIST
-- Check server status:
SHOW STATUS LIKE 'Threads%';
SHOW STATUS LIKE 'Questions';If consistently out of resources:
- Upgrade server hardware (RAM, CPU, storage)
- Add read replicas to distribute load
- Implement connection pooling (e.g., ProxySQL, MySQL Router)
- Archive old data to reduce table size
Use a connection pool and add retry logic in your application so transient drops are recovered automatically. A pool also reconnects transparently and is the correct place for waitForConnections, connectionLimit, and queueLimit.
JavaScript/Node.js (mysql2):
const mysql = require('mysql2/promise');
// Pool options like waitForConnections/connectionLimit/queueLimit
// are valid on createPool(), NOT on createConnection().
const pool = mysql.createPool({
host: 'localhost',
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: 'mydb',
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0,
// Per-query connect/timeout (ms):
connectTimeout: 30000,
});
// Retry logic for lost-connection errors:
async function queryWithRetry(sql, params = [], maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
// pool.query() acquires, uses, and releases a connection automatically
return await pool.query(sql, params);
} catch (err) {
const retryable = ['PROTOCOL_CONNECTION_LOST', 'ECONNRESET', 'ETIMEDOUT'];
if (retryable.includes(err.code) && i < maxRetries - 1) {
console.log(`Connection lost, retrying... (${i + 1}/${maxRetries})`);
await new Promise((r) => setTimeout(r, 1000));
} else {
throw err;
}
}
}
}Store credentials in environment variables or a secrets manager rather than hardcoding them.
Python (mysql-connector-python):
import os
import mysql.connector
from time import sleep
def query_with_retry(sql, params=None, max_retries=3):
for i in range(max_retries):
try:
connection = mysql.connector.connect(
host='localhost',
user=os.environ['DB_USER'],
password=os.environ['DB_PASSWORD'],
database='mydb',
connection_timeout=30,
)
cursor = connection.cursor()
cursor.execute(sql, params or ())
rows = cursor.fetchall()
cursor.close()
connection.close()
return rows
except mysql.connector.Error as err:
# 2006 = MySQL server has gone away, 2013 = Lost connection during query
if err.errno in (2006, 2013) and i < max_retries - 1:
print(f"Connection lost, retrying... ({i + 1}/{max_retries})")
sleep(1)
else:
raiseIf connecting over a network:
# Test connectivity:
ping mysql.example.com
# Check for packet loss:
ping -c 100 mysql.example.com
# Monitor the route with MTR:
mtr mysql.example.comIf double-NAT or firewall issues exist:
- Use a VPN or SSH tunnel
- Configure the firewall to allow persistent connections
- Enable TCP keepalive so idle connections aren't silently dropped
Keep connections alive and stable with TLS (recommended over public networks). Create the user requiring TLS, and prefer verifying the server certificate from the client rather than only REQUIRE SSL:
-- Require an encrypted connection for this user (MySQL 8.0):
CREATE USER 'appuser'@'%' IDENTIFIED BY 'use-a-strong-secret'
REQUIRE SSL;
-- Stronger: require a client certificate signed by your CA:
-- REQUIRE X509;On the client, point at the CA and verify the server identity so encryption isn't downgraded silently. For the mysql CLI:
mysql --ssl-ca=/path/to/ca.pem \
--ssl-mode=VERIFY_IDENTITY \
-h mysql.example.com -u appuser -pAvoid disabling certificate verification (for example --ssl-mode=DISABLED or trusting any certificate); that exposes traffic and credentials to interception. Only do so on a fully trusted private network, and prefer fixing the CA trust instead.
ERROR 2013 is often a symptom of deeper issues: chronic underprovisioning manifests as timeouts, inefficient queries overwhelm the server, and network instability is common in cloud environments. The best fix is architectural: add connection pooling (ProxySQL, MySQL Router), implement query timeouts at the application level, use read replicas to distribute load, and monitor with tools like Percona Monitoring & Management (PMM). If using managed MySQL (AWS RDS, Google Cloud SQL, Azure Database), check the provider's timeout limits—they may override your my.cnf settings. For Percona XtraDB Cluster or Galera, ERROR 2013 may indicate replication lag; check SHOW STATUS LIKE 'wsrep%'; in those cases.
ER_WINDOW_DUPLICATE_NAME (3591): Duplicate window name
How to fix ER_WINDOW_DUPLICATE_NAME (3591) in MySQL window functions
ERROR 1064: You have an error in your SQL syntax
How to fix "ERROR 1064: You have an error in your SQL syntax" in MySQL
ERROR 1054: Unknown column in field list
Unknown column in field list
ER_WINDOW_RANGE_FRAME_NUMERIC_TYPE (3589): RANGE frame requires numeric ORDER BY expression
RANGE frame requires numeric ORDER BY expression in MySQL window functions
CR_ALREADY_CONNECTED (2058): Handle already connected
How to fix "CR_ALREADY_CONNECTED (2058): Handle already connected" in MySQL