Fix PostgreSQL Idle-in-Transaction Timeout: Configuration and Best Practices
Quick Answer
- What to do: Set
idle_in_transaction_session_timeoutto automatically terminate sessions stuck in idle transactions, preventing connection pool exhaustion and lock contention. - First check: Run
SHOW idle_in_transaction_session_timeout;to see current value (default is 0, meaning disabled). QuerySELECT * FROM pg_stat_activity WHERE state = 'idle in transaction';to identify problematic sessions. - Minimal fix: Execute
ALTER SYSTEM SET idle_in_transaction_session_timeout = '900000';thenSELECT pg_reload_conf();to set a 15-minute timeout without restarting PostgreSQL. - Applicable versions: PostgreSQL 9.6 and later. Works with all connection poolers (PgBouncer, pgpool-II) and application frameworks (Python, Java, Node.js, Ruby, Go).
- Critical boundary: This parameter only affects sessions idle in a transaction—it does not terminate sessions idle outside a transaction or sessions executing long-running queries.
What Problem It Solves
Applications that open transactions but fail to commit or rollback them create a class of resource leaks that degrade database performance over time. Each idle-in-transaction session holds locks, prevents autovacuum from cleaning dead tuples, and consumes a connection slot from the pool. In high-concurrency environments, even a few leaked transactions can cascade into:
- Connection pool exhaustion, causing new requests to hang or fail
- Deadlock chains as held locks block other transactions
- Bloating tables because autovacuum cannot remove tuples visible only to the idle transaction
- Application-level timeouts and degraded user experience
The idle_in_transaction_session_timeout parameter solves this at the database kernel level, requiring zero application code changes. When a session remains idle in a transaction beyond the configured threshold, PostgreSQL terminates the session with a clear error message, allowing the application's connection pool to detect the failure and reconnect.
Root Cause Analysis
An idle-in-transaction session occurs when a client:
- Issues
BEGIN(or any statement that implicitly starts a transaction) - Executes one or more queries
- Stops sending queries without committing or rolling back
Common root causes include:
- Application bugs: Missing
commit()orrollback()in error-handling paths - Network interruptions: Client disconnects mid-transaction without cleanup
- Connection pool misconfiguration: Pool returns connections that still have open transactions
- Long-running user workflows: Web applications that keep database transactions open while waiting for user input
- ORM issues: Object-relational mappers that lazily manage transactions and fail to close them
The timeout acts as a circuit breaker. When triggered, PostgreSQL logs the termination and releases all resources held by that session.
Minimal Working Configuration
Global Setting (Recommended for Production)
SQL-- Set timeout to 15 minutes (900000 milliseconds) ALTER SYSTEM SET idle_in_transaction_session_timeout = '900000'; -- Apply without restart SELECT pg_reload_conf(); -- Verify the setting SHOW idle_in_transaction_session_timeout;
Session-Level Setting (For Testing or Specific Users)
SQL-- Must be set before BEGIN SET idle_in_transaction_session_timeout = '600000'; -- 10 minutes -- Now start your transaction BEGIN; -- ... queries ... COMMIT;
Configuration File (postgresql.conf)
INI# Add to postgresql.conf for persistent configuration idle_in_transaction_session_timeout = 15min
After editing the file, reload with pg_ctl reload or SELECT pg_reload_conf();.
Parameters and Environment Variables
| Parameter | Required | Default | Description |
|---|---|---|---|
idle_in_transaction_session_timeout | Yes | 0 (disabled) | Time in milliseconds after which an idle-in-transaction session is terminated. Set to 0 to disable. |
Recommended values by environment:
| Environment | Value | Rationale |
|---|---|---|
| Development | 300000 (5 min) | Catch transaction leaks quickly during testing |
| Staging | 600000 (10 min) | Balance between catching issues and avoiding false positives |
| Production | 900000–1800000 (15–30 min) | Allow legitimate long transactions while protecting resources |
Important notes:
- The value is in milliseconds. Use single-quoted strings with time units for readability:
'15min','600s','900000ms'. - Setting this parameter does not require a server restart—only a reload.
- The parameter cannot be set inside a transaction block. Use
ALTER SYSTEMor set it beforeBEGIN. - In replication environments, configure this on each standby independently—it does not replicate from the primary.
Common Errors and Fixes
| Error Message | Cause | Solution |
|---|---|---|
ERROR: idle-in-transaction session timeout (PID 12345) terminated | Session exceeded the timeout | Review application transaction logic. Add retry logic to handle this error and re-establish the connection. |
FATAL: terminating connection due to idle-in-transaction timeout | Session was terminated by the timeout | This is expected behavior. Ensure your application implements automatic reconnection (most connection pools do this by default). |
WARNING: SET idle_in_transaction_session_timeout requires a server reload | ALTER SYSTEM SET was used without reloading | Execute SELECT pg_reload_conf(); or run pg_ctl reload to apply the change. |
ERROR: parameter "idle_in_transaction_session_timeout" cannot be set after connection start | Attempted to set the parameter inside a transaction | Set the parameter before BEGIN, or use ALTER SYSTEM for global configuration. |
Production Notes and Security Checks
Deployment Checklist
- Start conservatively: Begin with 30 minutes (
1800000) in production, then reduce to 15 minutes after monitoring for false positives. - Monitor terminated sessions: Query
pg_stat_activityfor sessions withwait_event = 'timeout'and check PostgreSQL logs foridle-in-transaction session timeoutentries. - Log analysis: Use
grep 'idle-in-transaction session timeout' /var/log/postgresql/postgresql-*.logto track frequency and affected PIDs. - Application retry: Ensure your application catches the
FATALerror and retries the transaction. Most ORMs and connection pools (HikariCP, psycopg2 pool, node-postgres pool) handle this automatically. - Avoid configuration drift: Use
postgresql.confor infrastructure-as-code tools (Ansible, Terraform) rather thanALTER SYSTEMto maintain consistent configuration across environments.
Security Considerations
- Use a database user with
ALTER SYSTEMprivilege (typically superuser) to change this parameter - Enable
log_connections = onandlog_disconnections = onto audit terminated sessions - Consider setting
log_min_error_statement = errorto capture the SQL that was running when the timeout fired
Cloud Database Configuration
| Provider | Configuration Method | Notes |
|---|---|---|
| AWS RDS | Parameter group → idle_in_transaction_session_timeout | Requires instance reboot. Maximum value is 24 hours (86400000 ms). |
| Azure Database for PostgreSQL | Server parameters page | Changes apply immediately. Check service tier limits. |
| Google Cloud SQL | Database flags → idle_in_transaction_session_timeout | Apply and restart instance. |
FAQ
Q: How do I monitor sessions terminated by this timeout?
A: Query pg_stat_activity for sessions with state = 'idle in transaction' and wait_event = 'timeout'. For historical tracking, search PostgreSQL logs for idle-in-transaction session timeout. Tools like pgBadger can parse log files and generate reports on terminated sessions. You can also track the pg_stat_database counters xact_commit and xact_rollback to see if rollbacks spike after enabling the timeout.
Q: What is the difference between idle_in_transaction_session_timeout and statement_timeout? Can I use both?
A: Yes, use both—they address different problems. statement_timeout limits how long a single SQL statement can execute (e.g., a slow query). idle_in_transaction_session_timeout limits how long a session can sit idle inside a transaction without executing any statement. For example, if a transaction runs a complex JOIN that takes 2 minutes, statement_timeout of 30 seconds would kill it. If the application then pauses for 20 minutes before committing, idle_in_transaction_session_timeout of 15 minutes would kill it. Recommended: set statement_timeout = '30s' to '5min' and idle_in_transaction_session_timeout = '15min' to '30min'.
Q: How do I clean up existing idle-in-transaction sessions before enabling the timeout?
A: The timeout only affects sessions started after it is enabled. To clean up existing sessions, query pg_stat_activity and terminate them manually:
SQLSELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE state = 'idle in transaction' AND state_change < now() - interval '15 minutes';
Run this before enabling the timeout to ensure a clean baseline.