PostgreSQL "FATAL: password authentication failed for user": find the real reason
Fix PostgreSQL "password authentication failed" and "Peer authentication failed": read the server log, check pg_hba.conf, reset the password, and handle Docker.
by OhSudo ··
Not sure this is your cause?Find it step by step: Can't connect to the database →
Part of a learning path:SQL and PostgreSQL, step by step
You connect to PostgreSQL and get:
$ psql -h localhost -U app -d shopPassword for user app:psql: error: connection to server at "localhost" (127.0.0.1), port 5432 failed: FATAL: password authentication failed for user "app"Like most databases, PostgreSQL tells the client as little as possible: the same message for a wrong password, a user that doesn’t exist, or a password stored in a format the server won’t accept. The server log tells you which one it is, and it also says which line of pg_hba.conf (the file that decides who may connect, and how) handled your connection.
TL;DR
# 1. The real reason: the server log, right after a failed loginsudo tail -n 20 /var/log/postgresql/postgresql-16-main.log # Debian/Ubuntusudo journalctl -u postgresql -n 20 # RHEL family (or the log folder in the data directory)
# 2. Set a new password, as the postgres superusersudo -u postgres psql -c '\password app'1. Read the server log
On Debian and Ubuntu, the file name has the version and cluster name: /var/log/postgresql/postgresql-<version>-main.log. On RHEL-family systems, check sudo journalctl -u postgresql or the log/ folder inside the data directory (/var/lib/pgsql/data/log/ for the distribution’s package). Every failed login leaves two lines:
FATAL: password authentication failed for user "app"DETAIL: Connection matched file "/etc/postgresql/16/main/pg_hba.conf" line 125: "host all all 127.0.0.1/32 scram-sha-256"The DETAIL line is the answer. When it only names the pg_hba.conf line, as here, the password is wrong: with scram-sha-256, the default since PostgreSQL 14, the server gives no other reason. Any other cause adds a line before it. Older versions (before 16) write Connection matched pg_hba.conf line 125 without the file name.
| DETAIL in the log | What it means | Fix |
|---|---|---|
Only Connection matched …, with scram-sha-256 |
Wrong password | Section 3 |
Password does not match for user "app". |
Wrong password, with a password line in pg_hba.conf, or an md5 line and a password stored as MD5 |
Section 3 |
Role "app" does not exist. |
The user name is wrong, or the user was never created | CREATE ROLE app LOGIN PASSWORD '…'; — check the spelling first (\du) |
User "app" has no password assigned. |
The role exists without a password | Section 3 |
User "app" does not have a valid SCRAM secret. |
The password was stored as MD5, but pg_hba.conf asks for SCRAM |
Section 4 |
2. “Peer authentication failed”
psql: error: connection to server on socket "/var/run/postgresql/.s.PGSQL.5432" failed: FATAL: Peer authentication failed for user "app"This is not about a password at all. Without -h, psql connects through a local Unix socket, and the default pg_hba.conf uses peer authentication there: the Linux user name must be the same as the database user name. You are guest on Linux, you asked for app → refused. That’s why sudo -u postgres psql works, and psql -U postgres doesn’t.
Two ways out:
- Connect over TCP, which uses passwords:
psql -h localhost -U app -d shop. Applications usually connect this way anyway. - Or change the
localline inpg_hba.conffrompeertoscram-sha-256(see below), if you really need passwords on the socket.
3. Set a new password
As the postgres superuser:
sudo -u postgres psql\password app\password asks for the new password twice and sends it already hashed, so it doesn’t end up in your shell history or in the server log. ALTER ROLE app PASSWORD '…'; works too, but the password then appears in plain text in ~/.psql_history.
If you need a new value, the Password Generator has a shell-safe option, so it won’t need encoding in a connection URL.
Then test exactly the way the application connects — same host, same user, same database: psql -h localhost -U app -d shop.
If the password is right in psql but the app still fails, check how the app passes it:
- In a connection URL, special characters must be URL-encoded:
@becomes%40,:becomes%3A,/becomes%2F— the URL encoder does it for you. PGPASSWORDin the environment or a~/.pgpassfile can send an old password without you noticing.
4. MD5 vs. SCRAM
Since PostgreSQL 14, passwords are stored as SCRAM-SHA-256 by default, and new pg_hba.conf files ask for scram-sha-256. A role whose password was set long ago (or copied from an older server) may still have an MD5 hash, which a scram-sha-256 line can’t check:
SELECT rolname, left(rolpassword, 14) FROM pg_authid WHERE rolname = 'app';-- md5… = old MD5 hash; SCRAM-SHA-256$… = new formatSet the password again (section 3). With password_encryption = 'scram-sha-256' (the default), it’s stored in the new format. Clients need libpq 10 or newer for SCRAM — anything from the last several years is fine.
5. Check pg_hba.conf
PostgreSQL reads pg_hba.conf from the top and uses the first line that matches the connection type, database, user and address. A broad line near the top can win over the one you just added at the bottom. Find the file, and see how PostgreSQL itself reads it:
SHOW hba_file;SELECT line_number, type, database, user_name, address, auth_method, error FROM pg_hba_file_rules;The error column shows lines PostgreSQL couldn’t parse. After editing the file, reload — no restart needed:
SELECT pg_reload_conf();Connection types: local is the Unix socket (psql without -h), host is TCP (-h localhost, -h 10.0.0.5, every app over the network). A remote app needs a host line for its address and listen_addresses in postgresql.conf that isn’t only localhost — otherwise you get “Connection refused” instead.
6. Docker: the password only works on the first start
With the official postgres image, POSTGRES_PASSWORD (and POSTGRES_USER, POSTGRES_DB) are used only when the data directory is empty, the first time the container starts. If you change the password in docker-compose.yml later, the database in the volume still has the old one — and your app now sends the new one.
The database container’s log shows it, with the same lines as above:
docker compose logs db | grep -A1 FATALEither set the new password inside the container. Inside it, psql connects through the socket, which the image trusts, so it doesn’t ask for the old one. Use the POSTGRES_USER from your file (postgres if it doesn’t set one), or you’ll get role "postgres" does not exist:
-c '\password app' does the same and asks for the password instead of putting it in your shell history. Then start the app again with docker compose up -d.
Or, if the data doesn’t matter (a fresh dev setup), delete the volume so the image initializes again: docker compose down -v — this deletes all the data in it. Both ways can be tried in the terminal, which starts with the password just changed in compose.yaml. The same thing happens with MySQL and MariaDB images — see MySQL ERROR 1045.
Shell, we have a problem!
Similar, but different: Connection refused / could not connect to server means nothing is listening at that address and port — PostgreSQL isn’t running, or listen_addresses doesn’t include it. no pg_hba.conf entry for host "10.0.0.7", user "app", database "shop" means no line in pg_hba.conf matches the connection at all — add a host line for that address, as in this article. permission denied for table comes after a successful login — see PostgreSQL “permission denied for table”. If it refuses you without asking for a password, see Peer authentication failed. For everyday admin commands, see the PostgreSQL Admin Cheat Sheet. After adding POSTGRES_DB to a file whose volume already exists, the same “first start only” rule gives database does not exist.
$ ./practice.sh
Practice this in the terminal
Open the Terminal Playground with this article’s scenario loaded. It is safe and runs in your browser.
Found this useful? Share it.
Found a mistake? Let us know at contact@ohsudo.com.