The “MySQL server has gone away” error occurs when your database connection drops before a query finishes—usually from timeout settings, packet size limits, or server restarts. In 2026, Docker and serverless stacks have made this more common. Fix it by adjusting wait_timeout, max_allowed_packet, or implementing connection pooling with health checks.
Database connection failures cost developers hours of debugging time every week. According to Percona’s 2025 MySQL survey, timeout-related errors account for 34% of all production database incidents reported by teams running containerized workloads. When your application loses its MySQL connection mid-query, users see failed transactions, incomplete data writes, and cryptic 500 errors. The fix is usually a two-line config change, but finding the right two lines requires understanding what actually broke.
This error has five common triggers. The server closed an idle connection that sat unused past wait_timeout (default 28800 seconds, or eight hours). Your query sent a packet larger than max_allowed_packet. The MySQL daemon restarted while your app held a connection. A network blip severed the TCP socket. Or your application held a connection open across a long-running operation without sending keep-alive traffic.
In containerized environments, orchestrators like Kubernetes can kill and restart pods without your application noticing until the next query attempt. I’ve seen this in production: a routine node drain triggered connection resets across twenty microservices, all logging the same “server has gone away” message within seconds.
The error message itself comes from the MySQL client library, not the server. It means the client tried to use a socket that no longer had a listening server on the other end.
Log into your MySQL server and run:
SHOW VARIABLES LIKE '%timeout%';
SHOW VARIABLES LIKE 'max_allowed_packet';
You’ll see wait_timeout, interactive_timeout, and max_allowed_packet values. Default wait_timeout is 28800 seconds (8 hours) for non-interactive connections. If your application opens a connection, runs a job that takes nine hours, then tries to query again, the connection is already dead.
Check your application logs for the timestamp of the error and your MySQL general log for disconnect events. Match them up. If the disconnect happened exactly wait_timeout seconds after the last successful query, you’ve found your culprit.
Edit /etc/mysql/my.cnf (or /etc/my.cnf, depending on your distro) and add these lines under [mysqld]:
[mysqld]
wait_timeout = 600
interactive_timeout = 600
max_allowed_packet = 64M
Then restart MySQL:
sudo systemctl restart mysql
A ten-minute timeout (600 seconds) works well for most web apps. Longer batch jobs need either shorter timeouts with proper connection retry logic, or connection pooling that validates connections before use.
Raising max_allowed_packet helps when you’re inserting large blobs, importing dumps, or running queries with huge IN clauses. The default 16MB limit breaks on surprisingly small datasets once you factor in base64-encoded images or serialized JSON.
Don’t set wait_timeout to an enormous number like 86400. Zombie connections will pile up and exhaust max_connections. Instead, fix your application’s connection handling.
Modern ORMs and database libraries include connection pools with validation. In Node.js with mysql2, enable connection checks:
const pool = mysql.createPool({
host: 'localhost',
user: 'appuser',
password: 'password',
database: 'mydb',
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0,
enableKeepAlive: true,
keepAliveInitialDelay: 0
});
Python’s SQLAlchemy offers pool_pre_ping, which tests each connection before handing it to your code:
engine = create_engine(
'mysql+pymysql://user:pass@localhost/db',
pool_pre_ping=True,
pool_recycle=3600
)
The pool_recycle parameter closes and replaces connections after one hour, well before wait_timeout can kill them. I’ve used this pattern in Django apps talking to RDS instances across flaky NAT gateways; the pre-ping caught 90% of stale connections before they caused errors.
Containers add another failure mode. When Docker restarts a MySQL container, existing TCP connections from other containers break immediately. The client sees “server has gone away” on the next query.
If you’re using Docker Compose, mount your my.cnf as a volume:
services:
db:
image: mysql:8.0
volumes:
- ./my.cnf:/etc/mysql/conf.d/custom.cnf
- db_data:/var/lib/mysql
In Kubernetes, a ConfigMap works the same way. Mount it at /etc/mysql/conf.d/ and MySQL will load it on startup.
For production clusters, run a MySQL operator like Percona Operator or Vitess. These handle connection draining during pod restarts. According to the Vitess project’s GitHub, over 400 companies now run Vitess in production to avoid exactly this class of connection error.
Changed my.cnf but still seeing the error? Verify the config loaded:
mysql -u root -p -e "SHOW VARIABLES LIKE 'wait_timeout';"
If it still shows the old value, MySQL didn’t read your file. Common mistake: editing /etc/mysql/my.cnf when your MySQL was built to read /etc/my.cnf, or vice versa. Run:
mysql --help | grep 'Default options' -A 1
That output lists every config file MySQL checks, in order.
Another trap: some cloud providers (RDS, Cloud SQL) ignore local config files entirely. You set parameters through their web console or CLI. AWS RDS requires a custom parameter group. Google Cloud SQL uses the gcloud CLI:
gcloud sql instances patch INSTANCE_NAME \
--database-flags=wait_timeout=600,max_allowed_packet=67108864
If your error only happens during specific queries, log the query size. A 20MB INSERT might exceed max_allowed_packet even if you raised it to 16MB, because the packet includes protocol overhead.
Yes. Stateful firewalls drop idle TCP connections after a timeout, usually 300-3600 seconds. If your firewall timeout is shorter than MySQL’s wait_timeout, the connection breaks even though MySQL thinks it’s still alive. Check your firewall’s connection tracking timeout and either raise it or lower MySQL’s wait_timeout to match.
It depends. If the error happened during a SELECT, no data was lost—just retry the query. If it happened during an INSERT, UPDATE, or DELETE, check whether the transaction committed before the connection dropped. Wrap critical writes in explicit transactions and check for errors after COMMIT. Unconfirmed transactions should be retried with idempotency keys.
Local development usually runs quick requests with fresh connections. Production workloads hold connections open longer, run larger queries, and encounter network instability between app and database servers. Load balancers, proxies, and NAT gateways also introduce their own timeout layers. Replicate production conditions in staging by adding artificial latency or running long-duration load tests.
Parse your application error logs for the string “MySQL server has gone away” and alert when the rate crosses a threshold. Tools like Datadog, New Relic, or Prometheus with Loki can run log-based alerts. On the MySQL side, track Aborted_connects and Aborted_clients in SHOW GLOBAL STATUS. A sudden spike indicates widespread connection failures.
Use connection pooling. Persistent connections (like PHP’s mysql_pconnect) keep one connection per PHP-FPM worker, which doesn’t scale and causes max_connections exhaustion. Modern connection pools recycle connections across requests, validate them before use, and gracefully handle timeouts. Libraries like HikariCP (Java), pgBouncer-style proxies, and built-in ORM pools all outperform naive persistent connections.