TL;DR — MySQL timeouts at a glance: The eight timeouts you actually need: wait_timeout (default 28800s — too high for most apps), interactive_timeout (CLI sessions), net_read_timeout / net_write_timeout (TCP read/write window), connect_timeout (handshake), innodb_lock_wait_timeout (deadlock kill, default 50s), max_execution_time (statement-level kill switch, MySQL 5.7+), and the connection-pool side: HikariCP maxLifetime must be lower than MySQL wait_timeout by ≥30s or you'll see 'communications link failure'. Pin each one to your workload, not the defaults.
MySQL's default timeout values were designed for traditional applications where a handful of long-lived connections talk to the database. Modern web applications work completely differently — connection pools opening and closing connections rapidly, microservices with unpredictable request patterns, and serverless functions that spin up connections on demand.
The gap between MySQL's defaults and what modern apps need is where most timeout-related incidents live. "MySQL server has gone away" errors. Connection pool exhaustion at 3am. Transactions holding locks for 8 hours because nobody thought to tune wait_timeout. This guide covers every timeout variable that matters, with production-validated settings for different workload types.
wait_timeout = 300— kill idle connections after 5 minutes (default is 8 hours — far too long)innodb_lock_wait_timeout = 20— abort deadlocked transactions faster (default 50s)max_execution_time = 10000— kill runaway SELECT queries after 10 seconds- HikariCP
idleTimeoutshould be 90% of MySQLwait_timeout— misalignment causes "server has gone away" innodb_rollback_on_timeout = ON— fully roll back timed-out transactions (default only rolls back the last statement)
Connection Timeouts
wait_timeout and interactive_timeout
These are the most impactful timeout variables for web applications. They control how long MySQL keeps an idle connection open before closing it.
wait_timeoutapplies to non-interactive connections — application pools, JDBC, mysql2, etc.interactive_timeoutapplies to interactive clients — themysqlCLI, MySQL Workbench, etc.
The default is 28,800 seconds — 8 hours. For a web app with a connection pool of 50 connections and peak traffic at noon, by 8pm those 50 connections have been sitting idle for hours. Each one is holding a thread on the MySQL server, consuming memory. With enough connections and long enough idle periods, you exhaust MySQL's thread cache and max_connections limit.
-- Production web application SET GLOBAL wait_timeout = 300; -- 5 minutes: kills idle app connections SET GLOBAL interactive_timeout = 1800; -- 30 minutes: for admin tools -- Verify they're set SHOW VARIABLES LIKE '%timeout%';
The HikariCP alignment trap
This causes "MySQL server has gone away" errors for probably 50% of the teams who report it. HikariCP (the most popular Java connection pool) holds connections in the pool indefinitely by default. When MySQL closes a connection after wait_timeout, HikariCP doesn't know — it tries to use the dead connection and gets the error.
The fix: set HikariCP's idleTimeout to 90% of MySQL's wait_timeout. This ensures HikariCP evicts idle connections before MySQL does.
# Spring Boot / HikariCP — align with MySQL wait_timeout=300 (5 minutes)
spring:
datasource:
hikari:
idle-timeout: 270000 # 4.5 minutes (90% of 300s wait_timeout)
max-lifetime: 1740000 # 29 minutes (stay below MySQL's max)
connection-timeout: 30000 # 30 seconds to get a connection from the pool
maximum-pool-size: 20
minimum-idle: 5
url: jdbc:mysql://host:3306/db?socketTimeout=30000&connectTimeout=10000
# Node.js (mysql2)
const pool = mysql.createPool({
waitForConnections: true,
connectionLimit: 20,
idleTimeout: 270000, // 4.5 minutes
});
# Python (SQLAlchemy)
engine = create_engine(
'mysql+pymysql://...',
pool_recycle=270, # recycle connections after 4.5 minutes
pool_pre_ping=True, # test connection before use (catches dead connections)
pool_timeout=30
)
connect_timeout
Controls how long MySQL waits for a client to complete the initial handshake. The default (10 seconds) is fine for most setups. Increase it only if you're behind a slow proxy or in a high-latency network.
SET GLOBAL connect_timeout = 15; -- increase for high-latency networks SET GLOBAL connect_timeout = 5; -- decrease for DoS protection (faster rejection)
Query Execution Timeouts
max_execution_time
max_execution_time applies only to SELECT statements. It does not affect INSERT, UPDATE, DELETE, or DDL. Default is 0 (disabled). This is the variable that prevents a bad query from running for hours and consuming resources.
-- Global limit: kill any SELECT taking longer than 10 seconds
SET GLOBAL max_execution_time = 10000; -- milliseconds
-- Session override: allow longer queries in analytics sessions
SET SESSION max_execution_time = 120000; -- 2 minutes for this session
-- Per-query override with optimizer hint:
SELECT /*+ MAX_EXECUTION_TIME(60000) */
customer_id, SUM(order_value)
FROM orders
WHERE order_date > '2024-01-01'
GROUP BY customer_id;
-- Monitor how often queries are being killed by this setting
SELECT
variable_name,
variable_value
FROM performance_schema.global_status
WHERE variable_name IN ('max_execution_time_exceeded', 'max_execution_time_set');
When a query exceeds max_execution_time, the client gets: ERROR 3024 (HY000): Query execution was interrupted, maximum statement execution time exceeded. This is a clean error — the query is rolled back, no locks are held.
Lock and Transaction Timeouts
innodb_lock_wait_timeout
When one transaction is waiting for a row lock held by another transaction, innodb_lock_wait_timeout controls how long it waits before giving up. Default: 50 seconds.
In high-concurrency OLTP systems, 50 seconds is far too long. A transaction stuck waiting for a lock for 50 seconds has typically already caused a cascading problem — connection pool exhaustion, user-facing timeouts, etc. Reduce this aggressively in transactional workloads.
-- High-concurrency OLTP: fail fast on lock contention SET GLOBAL innodb_lock_wait_timeout = 10; -- 10 seconds -- Analytics workloads: allow complex joins to wait longer SET GLOBAL innodb_lock_wait_timeout = 120; -- CRITICAL: enable full rollback on lock timeout -- Default behavior: only rolls back the last statement, leaving the transaction open! SET GLOBAL innodb_rollback_on_timeout = ON;
About innodb_rollback_on_timeout: This is one of the most surprising MySQL defaults. When a lock wait times out, MySQL by default only rolls back the last statement of the transaction, not the entire transaction. The transaction stays open, holding whatever locks it already acquired. This can cause lock chains that cascade through your application. Always enable full rollback in production.
lock_wait_timeout
Controls how long DDL operations wait for metadata locks. Unlike innodb_lock_wait_timeout (which handles row locks), this handles table-level metadata locks — relevant when running ALTER TABLE, RENAME TABLE, etc. Default: 31,536,000 seconds (1 year — effectively infinite).
-- Prevent ALTER TABLE from blocking indefinitely waiting for a long transaction SET GLOBAL lock_wait_timeout = 300; -- 5 minutes -- This matters in production: if a long transaction holds a metadata lock, -- ALTER TABLE will block all subsequent queries on that table until it gets -- the metadata lock. With a 1-year default, this can halt your app.
Network and I/O Timeouts
-- net_read_timeout: how long MySQL waits to receive data from client SET GLOBAL net_read_timeout = 30; -- default 30s, usually fine -- net_write_timeout: how long MySQL waits to write data to client SET GLOBAL net_write_timeout = 60; -- default 60s -- Increase for large result sets over slow networks: SET GLOBAL net_write_timeout = 120; -- For clients reading very slowly (e.g., streaming large exports): SET SESSION net_write_timeout = 300;
Configurations by Workload Type
High-traffic web application
[mysqld] # Kill idle connections quickly — prevent pool exhaustion wait_timeout = 300 interactive_timeout = 1800 # Fast deadlock resolution innodb_lock_wait_timeout = 10 innodb_rollback_on_timeout = ON innodb_deadlock_detect = ON # Kill runaway SELECT queries max_execution_time = 5000 # 5 seconds # Connection management connect_timeout = 10 max_connections = 500 thread_cache_size = 32
Analytics / data warehouse workloads
[mysqld] # Long-running queries need more time wait_timeout = 3600 interactive_timeout = 7200 max_execution_time = 300000 # 5 minutes — override per-query with hints # Allow complex analytics queries to wait for locks innodb_lock_wait_timeout = 120 innodb_rollback_on_timeout = ON # More conservative on connections (fewer, longer-lived) max_connections = 100
OLTP (e-commerce, fintech)
[mysqld] # Fail fast on lock contention — don't block checkout for 50s wait_timeout = 180 interactive_timeout = 600 innodb_lock_wait_timeout = 5 # 5 seconds: fail fast, retry at application layer innodb_rollback_on_timeout = ON innodb_deadlock_detect = ON # Strict query execution limit max_execution_time = 3000 # 3 seconds # Connection settings connect_timeout = 10
Monitoring and Troubleshooting
Find long-running transactions
-- Transactions running more than 30 seconds SELECT trx_id, trx_state, TIMESTAMPDIFF(SECOND, trx_started, NOW()) AS running_sec, trx_rows_locked, trx_rows_modified, trx_query FROM information_schema.INNODB_TRX WHERE TIMESTAMPDIFF(SECOND, trx_started, NOW()) > 30 ORDER BY running_sec DESC;
Find lock waits
-- Who is blocking whom SELECT r.trx_id AS waiting_trx, r.trx_mysql_thread_id AS waiting_thread, r.trx_query AS waiting_query, b.trx_id AS blocking_trx, b.trx_mysql_thread_id AS blocking_thread, b.trx_query AS blocking_query, TIMESTAMPDIFF(SECOND, r.trx_wait_started, NOW()) AS wait_seconds FROM information_schema.INNODB_TRX r JOIN performance_schema.data_lock_waits w ON r.trx_id = w.REQUESTING_ENGINE_TRANSACTION_ID JOIN information_schema.INNODB_TRX b ON b.trx_id = w.BLOCKING_ENGINE_TRANSACTION_ID ORDER BY wait_seconds DESC;
Monitor connection churn
-- Connection health metrics SELECT variable_name, variable_value FROM performance_schema.global_status WHERE variable_name IN ( 'Threads_connected', 'Threads_running', 'Max_used_connections', 'Aborted_connects', -- connection handshake failures 'Aborted_clients', -- clients that disconnected improperly 'max_execution_time_exceeded' ); -- Aborted_clients increasing rapidly = wait_timeout too short or app not closing connections -- Aborted_connects increasing = authentication failures or connect_timeout too short
Current timeout settings at a glance
SHOW VARIABLES WHERE Variable_name IN ( 'wait_timeout', 'interactive_timeout', 'connect_timeout', 'innodb_lock_wait_timeout', 'lock_wait_timeout', 'innodb_rollback_on_timeout', 'max_execution_time', 'net_read_timeout', 'net_write_timeout' );
Diagnosing Common Timeout Errors
"MySQL server has gone away" (error 2006)
Almost always caused by wait_timeout vs connection pool misalignment. Check:
- What is your
wait_timeout?SHOW VARIABLES LIKE 'wait_timeout' - What is your pool's
idleTimeoutorpool_recycle? - Is
pool_pre_ping/testOnBorrowenabled? (validates connections before use) - Is
max_allowed_packetlarge enough for your queries?
"Lock wait timeout exceeded" (error 1205)
A transaction waited for a row lock longer than innodb_lock_wait_timeout. Check:
- Is there a long-running transaction holding the lock? (query
INNODB_TRX) - Is
innodb_rollback_on_timeoutON? If not, the waiting transaction might still be open - Is
innodb_deadlock_detectON? (deadlocks should resolve faster than lock timeouts)
Queries killed by max_execution_time
Check max_execution_time_exceeded counter. If it's high, you either have runaway queries or the limit is too aggressive for your workload. Review slow query log for queries near the threshold.
Working with JusDB
Timeout misconfigurations are one of the most common sources of production incidents we investigate — and one of the easiest to fix once you know what to look for. The combination of MySQL defaults designed for the 2000s applied to modern connection-pooling architectures creates predictable failure modes.
If you're experiencing "server has gone away" errors, connection exhaustion, or lock wait timeouts, our MySQL consulting team can audit your configuration and fix the root cause. Most of these issues resolve in a single session.
Related reading: MySQL Performance Tuning Guide | MySQL Connection Management | MySQL InnoDB Cluster Guide
Frequently Asked Questions
What is mysql wait_timeout and what should it be set to?
wait_timeout is the seconds MySQL waits on an idle non-interactive connection before closing it. Default: 28800s (8 hours). Production rule: set it to 10–15 minutes (600–900s) for web apps, lower for serverless. Match it to your connection pool's max-idle so the pool refreshes connections before MySQL drops them.What's the difference between wait_timeout and interactive_timeout?
interactive_timeout applies to clients that connected with CLIENT_INTERACTIVE (typically the mysql CLI). wait_timeout applies to everything else (apps, JDBC, ORMs). On session establishment MySQL copies whichever applies into the session-level wait_timeout. You almost always tune wait_timeout, not interactive_timeout.What does net_read_timeout do and when does MySQL trigger it?
net_read_timeout is how long MySQL will wait for a packet from the client mid-query. Default 30s. If the network stalls (e.g. client paused mid-bulk-INSERT), MySQL kills the connection after this. Increase to 60–120s for batch loaders or pipelines streaming large payloads.How should HikariCP maxLifetime be configured against wait_timeout?
maxLifetime at least 30 seconds lower than MySQL wait_timeout (and any infrastructure timeout — load balancers often kill at 60–350s). Otherwise the pool will hand out a connection MySQL has already closed → 'CommunicationsException: Communications link failure'. Default HikariCP maxLifetime is 1800000ms (30 min).What is innodb_lock_wait_timeout and how does it differ from wait_timeout?
innodb_lock_wait_timeout (default 50s) is how long a transaction waits for a row lock before InnoDB kills the lock-acquisition with error 1205. wait_timeout kills idle connections with no active transaction. A blocked transaction holds open the connection — only innodb_lock_wait_timeout stops it.How do I diagnose 'MySQL server has gone away' errors?
maxLifetime), 2) packet exceeded max_allowed_packet (raise on both server and client to 64–256 MB), 3) network device killed an idle TCP session (set OS-level tcp_keepalive_time below the LB timeout).Can I set max_execution_time per query instead of globally?
SELECT /*+ MAX_EXECUTION_TIME(5000) */ ... FROM ... (time in milliseconds). Only kills SELECT statements. Server-side global value is max_execution_time; session value can also be set via SET SESSION max_execution_time = 5000.Why does AWS RDS have different timeout defaults than vanilla MySQL?
wait_timeout stays at 28800 but the load balancer (NLB/Proxy) typically kills idle connections at 60–350s, which causes silent connection drops your app sees first. Always set HikariCP maxLifetime below the LB timeout, not the MySQL timeout, on RDS.