Your PgBouncer log is filling with closing because: server login failed (or server login timed out), and application latency is climbing. PgBouncer accepts client connections fine. It can even reach PostgreSQL over the network. But every attempt to complete the backend login handshake fails, so no new server connections enter the pool.
The symptom pattern is distinctive: sv_login stays elevated while sv_idle drains toward zero and cl_waiting grows. Existing server connections keep working until they expire or are recycled, but nothing replaces them. The pool shrinks from the inside while clients pile up in the wait queue. If nothing is fixed, the pool empties completely and every client waits until query_wait_timeout (default 120s) fires.
This failure has a short list of causes, and most are credential or authentication configuration mismatches, not network or PostgreSQL faults.
What this means
“Server login failed” is PgBouncer reporting that the server-side leg of the proxy failed at the authentication step. The client leg (application to PgBouncer) is a separate authentication path with its own failure modes and its own log lines (auth failed, password authentication failed for client login). Do not confuse them. This article covers the backend leg only: PgBouncer acting as a client to PostgreSQL.
The sequence that produces the error:
- A client query needs a server connection and the pool has no
sv_idleconnection available. - PgBouncer opens a TCP connection to PostgreSQL and starts the startup/auth handshake.
- PostgreSQL rejects the login: wrong password, wrong auth mechanism,
pg_hba.confdenial, no free connection slots, or unusable credentials. - PgBouncer closes the connection, logs
closing because: server login failed, and waitsserver_login_retry(default 15 seconds) before trying again for that pool. - During the retry backoff, no new server connections are added. The effective pool size shrinks by one for each failing login slot.
PgBouncer exposes no error counters via any SHOW command, so the log line is your primary detection signal. The metrics only show the consequences.
flowchart TD
A[server login failed in log] --> B{Can you psql from PgBouncer host
with same user and password?}
B -- No: auth error --> C[Password or auth_type mismatch
userlist.txt vs pg_authid]
B -- No: hba rejection --> D[pg_hba.conf rejects
PgBouncer source IP]
B -- No: too many connections --> E[PostgreSQL at max_connections]
B -- Yes, works --> F{Using auth_query?}
F -- Yes --> G[auth_query or auth_user failure
check PostgreSQL log]
F -- No --> H[SCRAM hash mismatch
hash not byte-identical to pg_authid]Common causes
| Cause | What it looks like | First thing to check |
|---|---|---|
Password mismatch between userlist.txt/auth_file and PostgreSQL | Login failures for one specific user; other pools healthy | Connect directly with the same credentials from the PgBouncer host |
auth_type mismatch (md5 vs scram-sha-256) | FATAL: server login failed: wrong password type or SCRAM-related errors; often appears after a PostgreSQL upgrade | Compare PgBouncer auth_type and the hash format in userlist.txt against pg_authid.rolpassword |
| SCRAM secret not byte-identical | SCRAM hashes present in userlist.txt but logins still fail | Hash must be copied verbatim from pg_authid.rolpassword; a re-generated hash for the same password will not work |
pg_hba.conf rejecting PgBouncer’s IP | PostgreSQL log shows no pg_hba.conf entry for host; all pools to that backend fail | PostgreSQL server log, and the hba rules for the PgBouncer source address |
PostgreSQL at max_connections | FATAL: sorry, too many clients already on the PostgreSQL side; failures correlate with load peaks | pg_stat_activity count vs max_connections |
auth_query failure | Login failures even though direct credential test works; may affect many users at once | PostgreSQL log for the auth query error; auth_user permissions |
Quick checks
All read-only. Run these before changing anything.
# 1. Confirm the exact error and which user/database is failing
grep -i "server login failed\|server login timed out" /var/log/pgbouncer/pgbouncer.log | tail -20
# 2. Check the pool state: sv_login elevated, sv_idle draining, cl_waiting growing
psql -h 127.0.0.1 -p 6432 -U pgbouncer pgbouncer -c "SHOW POOLS;"
# 3. Check whether the failure is pool-specific or global
psql -h 127.0.0.1 -p 6432 -U pgbouncer pgbouncer -c "SHOW DATABASES;"
# 4. Verify current auth configuration
psql -h 127.0.0.1 -p 6432 -U pgbouncer pgbouncer -c "SHOW CONFIG;" | grep -E "auth_type|auth_file|auth_query|auth_user|server_login_retry"
# 5. Test the backend login directly from the PgBouncer host,
# with the same user PgBouncer uses
psql -h <pg-host> -p 5432 -U <user> -d <database> -c "SELECT 1;"
# 6. On PostgreSQL: is it rejecting connections, and why?
# (run on the PostgreSQL host)
tail -50 /var/log/postgresql/postgresql-*.log | grep -iE "FATAL|pg_hba|too many clients"
# 7. On PostgreSQL: how close is it to max_connections?
psql -h <pg-host> -U postgres -c \
"SELECT count(*), current_setting('max_connections')::int FROM pg_stat_activity;"
How to diagnose it
Scope the failure with SHOW POOLS. Look at
sv_login,sv_idle, andcl_waitingper pool. If exactly one(database, user)pool shows elevatedsv_loginwith drainingsv_idle, the cause is almost certainly credentials for that user (causes 1, 2, 3, or 6). If every pool targeting one backend is failing, suspectpg_hba.conf,max_connections, or backend reachability (causes 4 and 5).Read the log line carefully.
closing because: server login failedmeans PostgreSQL responded with an auth rejection.server login timed outmeans the handshake never completed, which points toward an overloaded or unreachable backend rather than a credential problem. Note the timestamp pattern: a failure that starts at a specific moment for all users suggests a config change, a password rotation, or a PostgreSQL restart.Reproduce the login by hand. From the PgBouncer host, connect directly to PostgreSQL with the exact user, database, and password from
userlist.txt(step 5 above). Three outcomes:- Password authentication failed: the secret in
userlist.txtdoes not match what PostgreSQL has. Fix the file or the PostgreSQL password. no pg_hba.conf entry for host: PostgreSQL is rejecting PgBouncer’s source IP or the auth method for that connection. Fixpg_hba.conf.too many clients already: PostgreSQL is out of connection slots.
- Password authentication failed: the secret in
If the direct login works, suspect the hash format, not the password. PostgreSQL 14 and later default
password_encryptiontoscram-sha-256. If passwords were re-set after an upgrade,pg_authidnow stores SCRAM verifiers whileuserlist.txtstill holds old MD5 hashes, or vice versa. PgBouncer’s defaultauth_typeismd5; if the stored secret is a SCRAM verifier, SCRAM is used automatically, but the verifier inuserlist.txtmust be copied byte-for-byte frompg_authid.rolpassword. A hash regenerated from the same password with a different salt will fail. The classic error here isserver login failed: wrong password type.If you use
auth_query, test that path separately. Withauth_query, PgBouncer connects asauth_userand runs a lookup query to retrieve credentials. The failure can be in theauth_userlogin itself (a SCRAM-only secret forauth_userinuserlist.txtis a known failure mode, because there is no client supplying SCRAM keys for that connection), in the query’s permissions, or in what the query returns. Check the PostgreSQL log for the failing auth query.Check PostgreSQL’s headroom. If failures correlate with traffic peaks, sum all
pool_sizevalues across every pool and every PgBouncer instance targeting this PostgreSQL, and compare tomax_connectionsminussuperuser_reserved_connections. If PgBouncer can demand more slots than PostgreSQL can give, login failures under load are guaranteed.Account for the retry backoff. After a failed login, PgBouncer waits
server_login_retry(default 15s) before retrying. During diagnosis this makes the failure look intermittent: one attempt every 15 seconds per failing pool. A slow trickle of failures in the log at 15-second intervals is the backoff, not partial recovery.
Metrics and signals to monitor
| Signal | Why it matters | Warning sign |
|---|---|---|
sv_login (SHOW POOLS) | Connections stuck in backend auth. Should be ~0 in steady state | Consistently > 0 and not draining |
sv_idle (SHOW POOLS) | Ready reserve. Drains as existing connections expire with no replacements | Trending to 0 while sv_login > 0 |
| Total server connections (sv_active + sv_idle + sv_used + sv_tested + sv_login) | Distinguishes login failure from pool exhaustion: declining total = backend connection failure; stable at pool_size = exhaustion | Declining over minutes |
cl_waiting / maxwait (SHOW POOLS) | Client impact. Grows as the pool starves | cl_waiting > 0 with maxwait climbing |
Log: server login failed / server login timed out | The only direct error signal; no SHOW counter exists | Any occurrence in production |
PostgreSQL: connection count vs max_connections | Detects slot exhaustion causing login rejection | > 80% of max_connections |
Fixes
Password mismatch
Update userlist.txt (or the file pointed to by auth_file) with the correct password and run RELOAD on the admin console. If the password was rotated on the PostgreSQL side, decide which side is authoritative and align them. Verify with a direct psql test before reloading.
auth_type / hash format mismatch
Align three things: PgBouncer’s auth_type, the secret format in userlist.txt, and the verifier format in pg_authid.rolpassword. The lowest-friction fix for mixed environments is usually to copy the SCRAM verifier verbatim from pg_authid.rolpassword into userlist.txt. Plain-text passwords in userlist.txt work with any password-based mechanism but are a security tradeoff; if you use them, lock down the file permissions. After changes, RELOAD and watch sv_login drain.
pg_hba.conf rejection
Add or correct the pg_hba.conf entry for PgBouncer’s source IP with the intended auth method, then reload PostgreSQL. Some managed PostgreSQL services use md5 in their hba rules even when passwords are stored as SCRAM; PostgreSQL’s own compatibility logic handles that for direct connections, but it is one more place a mechanism mismatch can surface through a proxy.
PostgreSQL at max_connections
Short term, terminate idle or stuck backends to free slots. Be careful with pg_terminate_backend during an incident: kill genuinely idle sessions, not active queries, or you convert a queuing problem into failed transactions. Long term, reduce total pool demand (sum of all pool_size values, across all PgBouncer instances, targeting this backend) to under roughly 80% of max_connections, or raise max_connections on PostgreSQL (requires restart, and increases per-connection memory). This is a capacity planning problem, not a PgBouncer tuning problem.
auth_query failure
Verify the auth_user can log in directly and has permission to execute the auth query. If auth_user has a SCRAM verifier in userlist.txt, replace it with a plain-text or MD5 secret, since PgBouncer must authenticate that user to PostgreSQL without any client supplying SCRAM keys. Also check the PostgreSQL log: if the auth query itself errors, PgBouncer logs the original server error, which usually names the problem directly.
Reduce blast radius while fixing
server_login_retry (default 15s) controls the backoff between failed attempts. Do not lower it to “recover faster” while credentials are still wrong; that just hammers PostgreSQL with failing logins. Fix the credentials first. The backoff is also why recovery looks slow after the fix: give it a retry cycle or two before concluding the fix did not work.
Prevention
- Alert on the log line. PgBouncer exposes no error counters via
SHOWcommands.server login failedexists only in the log, so log monitoring is the only way to catch this beforecl_waitingtells you via user impact. - Alert on the metric signature.
sv_login > 0sustained withsv_idledeclining is detectable from metrics and catches the failure even without log parsing. - Make password rotation a two-sided procedure. Any PostgreSQL password change for a proxied user must include a
userlist.txtupdate andRELOADin the same change window. Stale auth files are a classic silently-catastrophic state. - Re-audit auth configuration after every PostgreSQL major upgrade. Changes to
password_encryptiondefaults (SCRAM became the default in PostgreSQL 14) silently change verifier formats the next time passwords are set. - Keep pool demand under backend capacity. The sum of all PgBouncer pool sizes targeting a backend should stay under 80% of
max_connections. - Test auth changes on one pool first. A bad
auth_typeorauth_querychange applied globally converts one bad login into a full outage within oneserver_lifetimecycle.
How Netdata helps
- Pool state per (database, user): Netdata charts
sv_login,sv_idle,sv_active, and the other pool states together, so the signature “sv_login elevated while sv_idle drains” is visible as one picture instead of three separateSHOW POOLSpolls. - cl_waiting and maxwait correlation: watching client queue depth rise in the same dashboard as the draining pool confirms user impact and tells you how much time you have before
query_wait_timeoutstarts disconnecting clients. - Declining total server connections: the sum of server connection states over time distinguishes backend login failure (declining total) from pool exhaustion (stable at pool_size), which is the key diagnostic fork.
- Cross-layer view: correlating PgBouncer login pressure with PostgreSQL’s connection count against
max_connectionsshows whether the backend is rejecting logins due to slot exhaustion versus credential problems. - Post-fix verification: after a credential fix and
RELOAD, the drain ofsv_loginand the refill ofsv_idleconfirm recovery within one retry cycle.
Related guides
- How PgBouncer actually works in production: a mental model for operators
- PgBouncer pool exhaustion: clients queue, wait times climb, and the retry cascade
- PgBouncer pool utilization high: sv_active approaching pool_size before clients queue
- PgBouncer maxwait high: the oldest client waiter and how close it is to timing out
- PgBouncer avg_wait_time high: the latency the pool itself is injecting
- PgBouncer no more connections allowed (max_client_conn): the front door is full
- PgBouncer max_client_conn tuning: setting the client limit against real FD headroom
- PgBouncer capacity planning: runway for pools, clients, and PostgreSQL slots
- PgBouncer monitoring checklist: the signals every connection pooler needs
- PgBouncer monitoring maturity model: from survival to expert
- PgBouncer advisory locks in transaction mode: orphaned locks and mysterious contention
- PgBouncer LISTEN/NOTIFY not working: why pub/sub needs session pooling






