The 28P01 fatal password authentication failed for user error means PostgreSQL rejected the login because the password check failed for that role.
This error shows up in psql, GUI clients, app logs, CI jobs, and migration tools. It can happen after a new install, a password reset, a container rebuild, a hostname switch, or a change to server auth rules. The fix is usually quick once you confirm which server you are hitting and which auth rule matched your connection.
What 28P01 Means In PostgreSQL
28P01 is a SQLSTATE code in PostgreSQL for invalid_password. The server reached the password step for a role, then rejected the password that the client supplied. That is different from errors that say the server is down, the network is blocked, or the role does not exist.
The tricky part is that the same message can hide a few distinct situations. The password can be wrong, the client can be connecting to a different server than you think, the role can have no password set, or the server can be enforcing an auth method your client is not prepared to use. PostgreSQL decides what auth method to use by matching your connection against the first rule that fits in pg_hba.conf.
Start with a simple mental model. The client sends connection details, then the server picks an auth method for that connection. If that method is password based, the server compares what you typed against what it has stored. If the match fails, you get 28P01.
Fast Checks That Save Time
Before you reset anything, get a clean picture of the connection you are making. A surprising number of 28P01 reports are not bad passwords. They are good passwords sent to the wrong place.
| Symptom | Likely Cause | Quick Check |
|---|---|---|
| It worked yesterday, fails today | Password changed or a new auth method got enabled | Try psql with an explicit host and user |
| Works on one machine, fails on another | Different pg_hba.conf rule matches by IP | Compare client IP and the matching hba line |
| Docker rebuild broke the login | Volume kept old data, new env vars did not apply | Check whether PGDATA is persisted |
| Role exists, still fails | Role has no password or password hash type mismatch | Inspect the role in pg_authid as a superuser |
Use psql as your truth tool because it is direct and noisy in a helpful way. Run a connection with each part spelled out so you avoid hidden defaults.
psql "host=DB_HOST port=5432 dbname=DB_NAME user=DB_USER"
If you get a password prompt, type the password once, slowly, and watch for typing layout layout issues. If you do not get a prompt, the failure may be happening before a password check, and 28P01 may be coming from a driver that simplified the message.
28P01 Password Authentication Failed For User Fix Steps
This section is a straight path you can follow in order. Stop when the error stops. The first fix that matches your setup is the one to apply.
Confirm You Are Logging Into The Right Server
When you have multiple PostgreSQL instances, the wrong host is the top trap. Local installs, containers, test clusters, and cloud instances can share the same port number on different network paths. A stored connection in a GUI can also keep an old hostname that still resolves.
- Print The Server Host — In psql, run
select inet_server_addr(), inet_server_port();so you see where you landed. - Print The Current User — Run
select current_user, session_user;to confirm the role name that the server sees. - Check DNS And Hosts — If you use a name like
dborpostgres, confirm it points to the server you mean in your current network.
Reset The Role Password The Safe Way
If you are sure you are on the right server, reset the password on the server side. Do it as a role that can alter other roles, often the postgres role or another admin role.
ALTER ROLE your_user WITH PASSWORD 'new_long_password';
Pick a password that avoids shell edge cases. Quotes, dollar signs, and backslashes can be valid passwords, yet they can get mangled when passed through env vars, YAML, or command lines. If you must use special characters, set the password inside the database with SQL instead of embedding it in a deployment file.
After you reset it, test with psql again using the same host and user. If it works there but your app still fails, the app is not sending the same password, or it is connecting as a different role.
Check Whether The Role Has A Password At All
Some roles are created without passwords, then later used from a host connection that requires one. In that case the server logs can show a detail that the role has no password assigned. You can verify the stored password field if you connect as a superuser.
-- as a superuser
select rolname, rolcanlogin, rolpassword is not null as has_password
from pg_authid
where rolname = 'your_user';
If has_password is false, set a password and retry. Also confirm rolcanlogin is true, since a role without login can exist and still fail authentication paths in confusing ways.
Server Rules That Trigger 28P01
PostgreSQL uses pg_hba.conf to decide how each connection must authenticate. A password error can be the surface signal of a rule mismatch. A common pattern is that a developer tests locally with trust or peer, then moves to TCP where the matching rule uses scram-sha-256, and the stored password hash is not compatible.
Find The Matching pg_hba.conf Line
Server logs can show the matching pg_hba.conf line number for a rejected connection when you raise log verbosity. If you do not have that, you can still infer the match by reading the file from top to bottom. The first rule that fits the connection type, database, role, and client host wins.
- List Connection Type — Local socket connections match lines that start with
local. TCP connections match lines that start withhost,hostssl, orhostnossl. - Match Client IP — A laptop on Wi-Fi can match a different subnet than a container in a bridge network.
- Match Database And Role — Wildcards like
allcan capture your session earlier than you expect.
Once you spot the matching line, read the auth method at the end. For password based methods, you will see values such as scram-sha-256 or md5. For non password methods, you might see peer, ident, or a certificate based method.
Scram And Md5 Mismatches
If your pg_hba.conf line uses scram-sha-256, the role needs a SCRAM verifier stored on the server. Newer PostgreSQL versions default to SCRAM for password storage, yet older clusters or upgraded clusters can still store MD5 hashes. If the stored hash type does not match what the server expects for that auth method, logins can fail in ways that look like bad passwords.
The clean fix is to reset the role password while password_encryption is set to the format you want for new passwords. Then the stored verifier will match the auth method you enforce.
SHOW password_encryption;
-- set in postgresql.conf or with ALTER SYSTEM
Peer And Ident Confusion On Local Logins
On many Linux installs, local socket logins use peer authentication by default. That means the server trusts the operating system user name, not a database password. If you run psql -U postgres as your own OS user, the server can reject it because the OS user does not match the database role. Some tools then show a password style message even when the method was not password based.
If you intend to use passwords for local logins, adjust the relevant local line in pg_hba.conf to a password method and reload PostgreSQL. If you intend to use peer, run psql as the matching OS user or connect over TCP with an explicit host.
Client Side Traps That Look Like Bad Passwords
Once server rules are sane, the remaining failures tend to be client input problems. These are subtle because you can stare at a password in a config file and still be sending a different value on the wire.
Connection Strings And Special Characters
In URLs, characters like @, :, and # can change meaning unless they are percent encoded. If your app uses a DATABASE_URL and the password contains one of those characters, the parser can split it and send only part of the password. The fix is to percent encode the password or switch to a DSN format where each parameter is separate.
- Encode URL Passwords — Replace reserved characters with percent forms so the URL parser keeps the password intact.
- Prefer Named DSNs — Use
host=,user=,password=style strings where the password is not parsed as a URL. - Trim Hidden Whitespace — Remove trailing spaces and newlines in env vars, .env files, and secrets managers.
.pgpass And GUI Stored Passwords
psql and many tools can read cached credentials. A stale entry in ~/.pgpass can cause repeated failures even after you typed a fresh password once. GUI tools can also store passwords per connection profile and keep using them even when you edit a shared URL elsewhere.
- Temporarily Disable .pgpass — Rename the file, test once, then restore it after you update the entry.
- Reenter Password In The Client — Delete the saved password field in the profile, then paste the new password.
- Log The Final DSN — Print the host, port, db, and user that the app will use, with the password redacted.
Prevent The Next 28P01
After the fix, lock in the working connection details and make password changes boring. A small routine saves hours later.
- Write One Smoke Test — Keep a saved
psqlcommand that connects and runsselect 1;. - Rotate Secrets In Order — Change the role password, update the app secret, restart the app, then run the smoke test.
- Keep Auth Rules Readable — Leave short comments next to
pg_hba.conflines so the next edit is less risky.
For official background, keep these pages handy: PostgreSQL error codes and client authentication.
As a final sanity check, search your logs for the lowercase string 28p01 fatal password authentication failed for user. If you see it only from one host or one service, you have narrowed it to a single connection path. If you see 28p01 fatal password authentication failed for user across many services at the same time, suspect a rotated secret that did not roll out.
28P01 Fatal Password Authentication Failed For User
If you still see the message after a reset and a psql test, recheck host and port. Many remaining cases come from pg_hba.conf matching a different rule than you thought.
