Give each application role its own statement timeout and connection limit using ALTER ROLE. That caps how long one query can run and how many sessions one service can hold, so a bug, a scraper or an injected expensive query cannot starve everyone else. Avoid setting these globally in postgresql.conf.
Why this is a security control
Availability is part of security. An attacker who finds a search endpoint that builds a slow query can tie up connections without stealing a row. A leaky service can do the same by accident. Limits per role turn an outage for all into a failure for one service.
The settings and what the docs say
| Setting | What it does | Default |
|---|---|---|
| statement_timeout | Aborts any statement that runs longer than the value | 0, which disables it |
| idle_in_transaction_session_timeout | Ends sessions left idle inside an open transaction | Check your version's default |
| idle_session_timeout | Ends sessions idle outside a transaction | 0, which disables it |
| transaction_timeout | Ends any session whose transaction runs too long, in newer versions | 0, which disables it |
| CONNECTION LIMIT on the role | Caps concurrent connections for that role | Set it per role |
The documentation notes that setting statement_timeout in postgresql.conf is not recommended because it would affect all sessions. That is why per role is the usual place. Your migration job and your web service need different numbers.
How to set them per role
ALTER ROLE app_runtime CONNECTION LIMIT 40;
ALTER ROLE app_runtime SET statement_timeout = '5s';
ALTER ROLE app_runtime SET idle_in_transaction_session_timeout = '30s';
ALTER ROLE app_migrator SET statement_timeout = '0';
ALTER ROLE ... SET gives the role a session default for the parameter. The reference page adds two details worth knowing. The values take effect at login, and SET ROLE or SET SESSION AUTHORIZATION do not process role specific settings. If your pool logs in once and then switches roles, the limits will not follow.
Choosing numbers
- Measure your normal slowest request in a staging copy.
- Set the timeout to a small multiple of that, then watch for aborted statements.
- Give reporting jobs their own role with a longer timeout rather than raising the web role.
- Set the connection limit below the server maximum, leaving room for admin sessions and the migration role.
- Alert on timeouts. A burst of them is a signal, not just noise.
Connection pools change the math
A pool holds connections open. If ten service replicas each keep twenty, that is two hundred sessions from one role. Compare the pool size times replicas against the role limit before a scale up, not after. Our post on the PgBouncer SCRAM advisory is a reminder that poolers are part of your database attack surface too.
Related reading
See least privilege roles in PostgreSQL for what each role should be allowed to do, and retry storms for how retries after a timeout can make load worse.
Safeguard scans your dependencies, including drivers and pooling libraries, and can open fix pull requests when you turn that on. A free scan is at gold.safeguard.sh/scan.
Quick check
Does a statement timeout stop SQL injection?
No. It limits damage from slow queries. Parameterized queries stop the injection.
Should I set statement_timeout in postgresql.conf?
The PostgreSQL docs say that is not recommended because it affects all sessions. Set it per role or per database.
Why did my role setting not apply?
Role settings take effect at login. They are not processed by SET ROLE or SET SESSION AUTHORIZATION.
Sources
Last reviewed: 19 October 2026.