Fix 'MySQL server has gone away' Error: Causes and Solutions

intermediate🗄️ MySQL2026-03-21| MySQL 5.7 / 8.x on Linux (Ubuntu, Debian, CentOS), macOS, Windows — affects PHP, Python, Node.js, Go, and any app with a persistent DB connection

Error Message

Error: MySQL server has gone away
#mysql#connection#timeout#gone away

It's 2 AM and your app is throwing errors

Your monitoring fires. The logs are full of Error: MySQL server has gone away. Queries that worked fine all day are now failing. The database is up — you can connect manually — so what's going on?

MySQL dropped the connection before your app finished using it. The client sent a query, but the server was no longer listening. Not a crash. Just a disconnect.

Quick diagnosis: find the real cause first

Before touching any config, figure out why the connection died. Three culprits account for 95% of cases:

  • Connection timeout — MySQL closed an idle connection before the app reused it
  • Packet too large — a query or blob exceeded max_allowed_packet
  • MySQL crashed or restarted — the server itself went away mid-session

Check MySQL error logs

# Ubuntu/Debian
tail -100 /var/log/mysql/error.log

# CentOS/RHEL
tail -100 /var/log/mysqld.log

# Or ask MySQL directly
SHOW VARIABLES LIKE 'log_error';

Check current timeout settings

SHOW VARIABLES LIKE '%timeout%';
SHOW VARIABLES LIKE 'max_allowed_packet';

Focus on wait_timeout and interactive_timeout. Stock MySQL defaults are 28800 seconds (8 hours). Many cloud providers set them much lower — AWS RDS often defaults to 600 seconds, some managed databases go as low as 60.

Check if the server restarted

-- Time since last restart
SHOW STATUS LIKE 'Uptime';

# On Linux, check system logs
journalctl -u mysql --since "1 hour ago"

Fix 1: Connection timeout (most common cause)

Your app opened a connection, left it idle, and MySQL killed it. The next query fails with MySQL server has gone away because the app still thinks the connection is alive.

Option A — Increase MySQL timeouts (server-side)

Edit /etc/mysql/mysql.conf.d/mysqld.cnf (or /etc/my.cnf):

[mysqld]
wait_timeout = 3600
interactive_timeout = 3600

Apply without restarting:

SET GLOBAL wait_timeout = 3600;
SET GLOBAL interactive_timeout = 3600;

Option B — Fix connection pooling in your app (the right fix)

Bumping the timeout just delays the problem. Sooner or later, a connection will still go stale. The durable fix is teaching your app to detect a dead connection and reconnect automatically.

Python (SQLAlchemy):

engine = create_engine(
    DATABASE_URL,
    pool_pre_ping=True,       # Test connection before using it
    pool_recycle=1800,        # Recycle connections every 30 minutes
    pool_timeout=30,
    pool_size=10
)

Node.js (mysql2):

const pool = mysql.createPool({
  host: 'localhost',
  user: 'root',
  database: 'mydb',
  waitForConnections: true,
  connectionLimit: 10,
  enableKeepAlive: true,      // Send keepalive packets
  keepAliveInitialDelay: 10000
});

// Always use pool.execute(), not a single persistent connection
const [rows] = await pool.execute('SELECT 1');

PHP (PDO):

// Avoid PDO::ATTR_PERSISTENT — it reuses connections across requests without checking if they're alive
// Catch the error and reconnect instead:
try {
    $stmt = $pdo->query($sql);
} catch (PDOException $e) {
    if (str_contains($e->getMessage(), 'server has gone away')) {
        $pdo = new PDO($dsn, $user, $pass, $options); // reconnect
        $stmt = $pdo->query($sql);
    } else {
        throw $e;
    }
}

Fix 2: max_allowed_packet too small

Does the error happen when inserting large text, images, or JSON blobs — but not on idle connections? Packet size is the culprit, not timeout.

-- Check current limit
SHOW VARIABLES LIKE 'max_allowed_packet';
-- Default is 4MB (4194304) in MySQL 5.7, 64MB in MySQL 8.0

Increase it in my.cnf:

[mysqld]
max_allowed_packet = 64M

Or apply live (takes effect for new connections only):

SET GLOBAL max_allowed_packet = 67108864;

Restart MySQL to make the change survive reboots:

sudo systemctl restart mysql

Fix 3: MySQL crashed or was OOM-killed

Low Uptime or a restart entry in the error log means the server itself went down — typically due to memory pressure. The OS killed MySQL before it could close connections cleanly.

# Check if the OOM killer hit MySQL
dmesg | grep -i 'killed process'
grep -i 'oom' /var/log/syslog | tail -20

If MySQL was OOM-killed, cap innodb_buffer_pool_size at 70–75% of available RAM. On a 4 GB server, that's about 2–3 GB:

[mysqld]
# For a server with 4GB RAM:
innodb_buffer_pool_size = 2G

Verify the fix

After applying changes, confirm the new values are actually in effect:

-- Confirm new timeout values
SHOW VARIABLES LIKE 'wait_timeout';
SHOW VARIABLES LIKE 'max_allowed_packet';

-- Watch connection errors in real time
SHOW STATUS LIKE 'Aborted_clients';
SHOW STATUS LIKE 'Aborted_connects';

A rising Aborted_clients count means clients are dropping connections without closing them properly — your connection pool config needs attention. Aborted_connects climbing instead points to authentication or network issues.

Want to stress-test your app-side reconnect logic? Drop wait_timeout to 10 seconds, wait 15, then fire a query through your app:

SET GLOBAL wait_timeout = 10;
-- Wait 15 seconds, then run a query through your app
-- No error = pool_pre_ping / reconnect logic is working

What to take away from this

  • Never hold idle DB connections open — always use a connection pool with pre_ping or keepalive. Even if MySQL's timeout is low, the pool will handle reconnects transparently.
  • Set max_allowed_packet to 64M before you ship — if your app handles file uploads or large JSON payloads, the 4 MB default will catch you off guard in production.
  • Watch Aborted_clients — a steady climb is an early warning that something is holding connections open or leaking them.
  • Managed databases have aggressive timeouts — RDS, PlanetScale, and Cloud SQL often default to 60–600 seconds. Set pool_recycle to half that value to stay safe.

Related Error Notes