Fix Postgres Password Authentication Failed
Chat2DB TeamFew PostgreSQL errors are as common, or as frustrating, as this one:
psql: error: connection to server at "localhost" (127.0.0.1), port 5432 failed:
FATAL: password authentication failed for user "app"The message (SQLSTATE 28P01, invalid_password) is deliberately vague. PostgreSQL never tells the client why authentication failed, because that would help an attacker. It reports the same error whether the password is wrong, the role does not exist, the role has no password at all, or the stored password hash uses a method the server will not accept. The real reason is written only to the server log.
This guide walks through a reliable diagnostic order: read the server log, confirm which pg_hba.conf rule matched, check the role and its password hash, and then fix the specific cause. It also covers the situations that trip people up most often: Docker volumes that ignore POSTGRES_PASSWORD, the md5 to scram-sha-256 change, special characters in connection URLs and environment variables that silently override your password.
Step 1: Read the Server Log First
The server log contains a DETAIL line that names the actual cause. Find the log location from a superuser session, or from the data directory if you cannot connect at all:
SHOW log_destination;
SHOW logging_collector;
SHOW log_directory; -- relative to data_directory unless absolute
SHOW data_directory;On Debian and Ubuntu packages the log is usually under /var/log/postgresql/. With Docker it is the container output:
docker logs my-postgres 2>&1 | grep -A2 "authentication failed"You will see one of a handful of DETAIL messages, and each points to a different fix:
| DETAIL in the server log | What it means |
|---|---|
Password does not match for user "app". | The role exists and has a password, but the one sent is wrong. |
Role "app" does not exist. | You are connecting with a user name that was never created, often a typo or the wrong cluster. |
User "app" has no password assigned. | The role was created without PASSWORD, so password auth can never succeed. |
User "app" does not have a valid SCRAM secret. | The rule requires scram-sha-256 but the stored hash is old md5. |
Connection matched file "/etc/postgresql/17/main/pg_hba.conf" line 118: ... | Tells you exactly which pg_hba.conf rule was used. |
The Connection matched line, logged alongside every authentication failure, is the most useful single piece of information in this whole guide. It tells you which rule and which authentication method the server applied.
Step 2: Check Which pg_hba.conf Rule Matched
PostgreSQL reads pg_hba.conf from top to bottom and uses the first line whose connection type, database, user and address match. It does not fall through to later lines if authentication fails. So a broad rule near the top can shadow the rule you think you are using.
You can inspect the parsed file without opening it:
SELECT line_number, type, database, user_name, address, netmask, auth_method, error
FROM pg_hba_file_rules
ORDER BY line_number;A typical file looks like this:
# TYPE DATABASE USER ADDRESS METHOD
local all postgres peer
local all all peer
host all all 127.0.0.1/32 scram-sha-256
host all all ::1/128 scram-sha-256
host appdb app 10.0.0.0/8 scram-sha-256Two details matter here:
localversushost.psql -U app appdbwithout-hconnects over the Unix socket and matcheslocallines. Withpeerauthentication, the server ignores any password and checks that your operating system user is also calledapp. That fails with a different error (Peer authentication failed), but people often fix it by adding-h localhost, which switches to ahostline and password authentication, where the real password problem then surfaces.localhostcan mean IPv6. Iflocalhostresolves to::1, the127.0.0.1/32line does not match. Make sure both IPv4 and IPv6 loopback lines exist.
After editing pg_hba.conf, reload the configuration. No restart is needed:
SELECT pg_reload_conf();Then query pg_hba_file_rules again and confirm the error column is empty for every line. A syntax error in one line means that line is skipped.
Step 3: Check the Role and Its Password Hash
Connect as a superuser (on Linux, sudo -u postgres psql uses peer auth and needs no password) and check the role:
SELECT rolname,
rolcanlogin,
rolvaliduntil,
CASE
WHEN rolpassword IS NULL THEN 'no password'
WHEN rolpassword LIKE 'SCRAM-SHA-256$%' THEN 'scram-sha-256'
WHEN rolpassword LIKE 'md5%' THEN 'md5'
ELSE 'other'
END AS password_type
FROM pg_authid
WHERE rolname = 'app';pg_authid is readable only by superusers. Look for these problems:
- No row: the role does not exist. Create it.
rolcanlogin = false: the role was created withCREATE ROLE(which defaults toNOLOGIN) instead ofCREATE USER. The server log saysrole "app" is not permitted to log in, a different error, but worth checking.rolvaliduntilin the past: the password has expired, and authentication fails with exactly the "password authentication failed" message.password_type = 'md5'whilepg_hba.confsaysscram-sha-256: the stored hash cannot be verified with SCRAM.no password: set one.
Step 4: Reset the Password Correctly
Resetting is often the fastest fix. Use \password in psql rather than ALTER ROLE ... PASSWORD 'plain text', because \password hashes the password on the client, so the plain text never appears in the server log or in pg_stat_statements:
SET password_encryption = 'scram-sha-256'; -- the default since PostgreSQL 14
\password appIf you must do it in SQL, for example in a provisioning script:
ALTER ROLE app WITH LOGIN PASSWORD 'N3w-Str0ng-Passw0rd';
-- clear an expiry date if one was set
ALTER ROLE app VALID UNTIL 'infinity';The hash type is decided by password_encryption at the moment the password is set. Changing password_encryption later does not convert existing hashes. That is why an upgrade from PostgreSQL 13 (where the default was md5) to 14 or later can break logins: the old md5 hashes are carried over, and if you also switch pg_hba.conf to scram-sha-256, every one of those users fails.
To find every role that still has an md5 hash:
SELECT rolname
FROM pg_authid
WHERE rolcanlogin AND rolpassword LIKE 'md5%';Reset each of those passwords with password_encryption = 'scram-sha-256'. Until you do, you can keep md5 in pg_hba.conf: an md5 rule accepts SCRAM hashes too, so it is the safe transitional setting. Note that PostgreSQL 18 deprecates MD5 passwords and logs a warning when one is set, so plan the migration rather than relying on md5 long term.
Step 5: Docker Ignores POSTGRES_PASSWORD on an Existing Volume
This one catches almost everyone who runs PostgreSQL in Docker. The official image reads POSTGRES_PASSWORD, POSTGRES_USER and POSTGRES_DB only when the data directory is empty, that is, on the very first start. If a volume already contains a database, changing the password in docker-compose.yml does nothing:
services:
db:
image: postgres:17
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: changed-this-later # ignored if pgdata already exists
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:You have two options. Either change the password inside the running database:
docker exec -it my-postgres psql -U app -d postgres -c "\password app"(Inside the container, socket connections use trust by default, so no password is needed.) Or, if the data is disposable, remove the volume so initialization runs again:
docker compose down -v # deletes the named volumes, and all data in them
docker compose up -dAlso check that you are connecting to the container you think you are. If a PostgreSQL server is installed on the host and listening on 5432, a container published on 5432:5432 may fail to bind, and your client ends up talking to the host server, which has a different app password, or no app role at all. ss -ltnp | grep 5432 (Linux) or lsof -i :5432 (macOS) shows which process owns the port.
Step 6: Check the Client Side
When the server log says Password does not match and you are sure the password is right, the client is usually sending something different.
Special characters in connection URLs
In a URI, characters such as @, :, /, #, ? and % must be percent-encoded. The password p@ss#1 breaks this URL:
postgresql://app:p@ss#1@db.example.com:5432/appdbEncode it instead:
postgresql://app:p%40ss%231@db.example.com:5432/appdbYou can generate the encoded value with Python: python3 -c "import urllib.parse; print(urllib.parse.quote('p@ss#1', safe=''))".
Environment variables and .pgpass
libpq-based clients (psql, pg_dump, psycopg, many drivers) read PGPASSWORD, PGUSER, PGHOST and the ~/.pgpass file. A stale entry can silently override what you typed. Check them:
env | grep ^PG
cat ~/.pgpass # format: host:port:database:user:passwordThe .pgpass file is ignored unless its permissions are 0600, and the first matching line wins.
Trailing whitespace and newlines
Passwords loaded from files, Kubernetes secrets or .env files often contain a trailing newline. echo "secret" | base64 encodes the newline too; use echo -n. If a password works when typed but not from your app, compare lengths.
Step 7: Cloud Databases
Managed services add a few twists:
- Amazon RDS / Aurora: if the instance uses IAM database authentication, the "password" is a short-lived token generated with
aws rds generate-db-auth-token, valid for 15 minutes. A token used after expiry fails with the same error. The master password can be reset in the console. - Azure Database for PostgreSQL Flexible Server: with Microsoft Entra authentication the password is an access token. Check which authentication mode the server is configured for.
- Supabase and Neon: connection poolers can require a different user name format (for example
user.projectrefon Supabase's pooler). Copy the connection string from the dashboard rather than building it by hand. - SSL requirements: if the provider requires TLS and your
pg_hba.confequivalent useshostssl, a non-TLS connection fails withno pg_hba.conf entryrather than a password error, which tells you it is not a password problem.
A Quick Checklist
Work through these in order and you will find the cause in minutes:
- Read the
DETAILandConnection matchedlines in the server log. - Confirm the matched
pg_hba.confline and method withpg_hba_file_rules; reload after edits. - Check
pg_authidfor the role: exists,LOGIN, not expired, SCRAM hash. - Reset the password with
\passwordunderpassword_encryption = 'scram-sha-256'. - With Docker, remember env vars only apply to an empty data directory.
- Percent-encode special characters in URLs and check
PGPASSWORDand~/.pgpass.
Once the connection works, it helps to save it in a client that stores credentials safely and lets you test them in one click. Chat2DB (opens in a new tab) keeps your PostgreSQL connections, including SSH tunnels and SSL settings, in one place, so you can confirm a new password works and immediately run the pg_authid and pg_hba_file_rules queries above in the same editor.
Summary
"Password authentication failed for user" is a single client-facing message for several different server-side problems. The server log always names the real one: a wrong password, a missing role, a role without a password, an expired password, or an md5 hash meeting a scram-sha-256 rule. Fix that specific cause, and keep in mind the two traps that account for most support threads: Docker volumes that keep the old password, and unencoded special characters in a connection URL.
