PostgreSQL error 28P01
password authentication failed for user
The server asked for a password and didn’t accept the one it got. Either the password is wrong, or the user doesn’t exist: PostgreSQL deliberately gives the same message for both.
FATAL: password authentication failed for user "inlet"
Tested on PostgreSQL 18.6 (also 14–17) · Updated 9 October 2026
What it means
PostgreSQL found a rule in pg_hba.conf that says this connection must sign in with a password
(scram-sha-256, md5 or password), asked for one, and rejected what your client sent.
The message is the same whether the password is wrong or the user doesn’t exist at all. That’s on purpose: telling strangers which user names are real would help someone guessing passwords. The server’s log says which it was; your client never does.
Common causes
- The password is wrong: a typo, an old password, or one copied with a trailing space or newline.
- The user doesn’t exist on this server: a different spelling or case, or you’re connected to a different server (port, container, environment) than you think.
- Special characters in a connection URL that weren’t percent-encoded, so the client sent a different password than the one you typed.
- The password was stored with an older method. A user whose password was set while
password_encryptionwasmd5can’t sign in through ascram-sha-256rule until the password is set again. - A pooler or proxy in between (PgBouncer, Supavisor, RDS Proxy) checks its own list of users and passwords, which may not match the database’s.
How to fix it
Check what the server saw
The server log has the real reason. Look for the lines just after the FATAL:
FATAL: password authentication failed for user "nobody"
DETAIL: Role "nobody" does not exist.
Connection matched file "/var/lib/postgresql/18/docker/pg_hba.conf" line 128: "host all all all scram-sha-256"
DETAIL tells you whether the role exists, and which pg_hba.conf line matched. On hosted databases
the log is in the provider’s console.
Reset the password
Connect as a superuser (or the role’s owner) and set it again:
ALTER ROLE app PASSWORD '<new password>';
From psql, \password app asks for it without putting it in your shell history. Setting it again
also stores it with the current password_encryption method, which fixes cause 4.
Make sure the user exists
SELECT rolname, rolcanlogin FROM pg_roles WHERE rolname = 'app';
No row: create it, or connect as the user you meant. Role names are case-sensitive when they were
created in double quotes: "App" and app are different roles. A row with rolcanlogin false is a
role without the LOGIN attribute; that one fails with a different message,
role "app" is not permitted to log in.
Encode special characters in URLs
In a URL, @, :, /, ?, # and % in the password must be percent-encoded: p@ss:word
becomes p%40ss%3Aword. See special characters in passwords.
Check the pooler
If you connect through PgBouncer, check its auth_file or auth_query. On hosted poolers (Supabase,
Neon, RDS Proxy), use the user name format and password the provider gives for the pooled
connection string, which can differ from the direct one.
Reproduce it
On PostgreSQL 18.6, with a host all all all scram-sha-256 rule, a wrong password for an existing
user:
PGPASSWORD=wrong psql -h localhost -p 54318 -U inlet -d inlet -c 'select 1'
psql: error: connection to server at "localhost" (::1), port 54318 failed: FATAL: password authentication failed for user "inlet"
A user that doesn’t exist gets the same message:
psql: error: connection to server at "localhost" (::1), port 54318 failed: FATAL: password authentication failed for user "nobody"
Through an md5 rule (port 54338 here) the message is identical. Only the server log tells the
two cases apart.
In Inlet
The connection window says it couldn’t sign in, shows the server’s message, and asks for the password right there. Type it and choose Connect; with Save in Keychain ticked, Inlet keeps it once the server accepts it, so you won’t be asked again.