PostgreSQL

FATAL: password authentication failed for user "postgres"

The error fires when PostgreSQL rejects the password the client sent — common when a role never got a password, when an old md5 hash meets a scram-sha-256 rule in pg_hba.conf, or when a Docker volume still holds an older password. Set the password with ALTER USER, then check the hash type.

Mahdi Ben RhoumaMahdi Ben Rhouma
Updated
6 min

You spin up PostgreSQL (locally, in Docker, or on a fresh VPS), type the password you set during install — and PostgreSQL answers FATAL: password authentication failed for user "postgres":

text
$ psql -U postgres -h localhost
Password for user postgres:
psql: error: connection to server on host "localhost" (127.0.0.1), port 5432 failed: FATAL:  password authentication failed for user "postgres"

It is one of the most-viewed PostgreSQL questions on Stack Overflow (Q19070362). The error message is the same for every cause; three account for almost every case:

  1. The role has no password set — common after a fresh install that only used peer auth.
  2. An md5 hash under a scram-sha-256 rule — the role's password was stored as md5 (older setups, or password_encryption = md5), and the matching pg_hba.conf line now requires scram-sha-256.
  3. The client sends a different password — a typo, a special character mangled by the shell or the connection URL, or a Docker volume initialised with an older password.

The combination people usually suspect first — an md5 line with a password stored as SCRAM — does not fail: the PostgreSQL docs state that in that case "SCRAM-based authentication will automatically be chosen instead" (Password Authentication).

The client never learns which cause fired, but the server log usually does — read the DETAIL line under the FATAL. Each row below was reproduced against PostgreSQL 18 (official Docker image, default scram-sha-256 rule) on 30 September 2026:

Server log DETAILCause
User "postgres" has no password assigned.1 — no password
User "postgres" does not have a valid SCRAM secret.2 — md5 hash under a scram-sha-256 rule
Connection matched file ".../pg_hba.conf" line N: "host all all all scram-sha-256" — and nothing else3 — a SCRAM password that is simply wrong
User "postgres" has an expired password.the role's VALID UNTIL date has passed

The third row is the one that confuses people: for a plain wrong password under SCRAM, PostgreSQL does not say the password differs; it only tells you which pg_hba.conf line matched. If the log names the line and no other reason, the stored password and the one the client sends are different.

On Docker, read the log with docker logs <container>; on a Debian/Ubuntu install, in /var/log/postgresql/.

Here are the fixes for each.

Fix 1 — Set a password on the role#

The most common case: the role exists but has no password. Peer auth let you in as the postgres OS user, but TCP connections require a password.

bash
sudo -u postgres psql
sql
ALTER USER postgres PASSWORD 'a-strong-dev-password';

That works for the default postgres superuser. For your own role (say mahdi), connect as the role owner first:

bash
sudo -u postgres psql -c "ALTER USER mahdi PASSWORD 'a-strong-dev-password';"

ALTER USER ... PASSWORD rewrites the stored verifier using whichever algorithm password_encryption is set to (default scram-sha-256 since PostgreSQL 14). Note that psql sends the password in clear text inside that SQL command, so it can land in the server log or ~/.psql_history; \password postgres inside psql hashes it client-side instead.

Fix 2 — Re-hash an md5 password before a scram-sha-256 rule#

If the log says does not have a valid SCRAM secret, the role's password is stored as md5 while the matching pg_hba.conf line requires scram-sha-256. A SCRAM rule cannot verify an md5 hash. The fix is to re-store the password as SCRAM, not to change the rule back.

Check how each role's password is stored:

sql
SELECT rolname, left(rolpassword, 14) AS stored_as
FROM pg_authid
WHERE rolpassword IS NOT NULL;

SCRAM-SHA-256$… is a SCRAM verifier; md5… is an md5 hash. PostgreSQL 18 already warns when it stores one — setting an MD5-encrypted password with the detail MD5 password support is deprecated and will be removed in a future release of PostgreSQL. Re-hash an md5 one:

sql
SET password_encryption = 'scram-sha-256';
ALTER USER postgres PASSWORD 'a-strong-dev-password';

Then check which rule applies to your connection:

bash
sudo -u postgres psql -c 'SHOW hba_file;'

The relevant line depends on whether you connect over TCP (host) or the local socket (local):

text
# IPv4 local connections:
host    all             all             127.0.0.1/32            scram-sha-256
# IPv6 local connections:
host    all             all             ::1/128                 scram-sha-256
# Local socket:
local   all             all                                     scram-sha-256

An md5 line keeps working with SCRAM-stored passwords, because PostgreSQL chooses SCRAM automatically. Moving the line to scram-sha-256 is still worth doing — SCRAM is available since PostgreSQL 10, and md5 is the weaker, legacy method — but only once every role that connects through it has been re-hashed, or those roles start failing with this exact error.

Reload — pg_hba.conf is re-read on reload, not on every connection:

bash
sudo systemctl reload postgresql
# or, if you start pg manually:
pg_ctl reload -D /var/lib/postgresql/data
Do not use trust to bypass

Setting the method to trust makes the error go away by disabling authentication entirely — any local user can connect as any role, including the superuser. Fine on an isolated throwaway VM, dangerous on any shared or developer machine. Use scram-sha-256 and a real password.

Fix 3 — Verify the password you typed#

Less common but easy to miss:

  • Trailing whitespace. psql reads the password via /dev/tty; some terminals strip or add whitespace depending on how the password is pasted. Type it manually to be sure.
  • Special characters in shell. If you pass the password via PGPASSWORD=foo\!bar psql ..., \! is interpreted by bash. Use single quotes (PGPASSWORD='foo!bar') or a .pgpass file with mode 0600.
  • Wrong user. psql -U postgres with a connection string -h localhost connecting as mahdi — the role name in the error message is the one PostgreSQL tried to authenticate. Re-read the error message.

Use a .pgpass file (avoids shell quoting and password prompts)#

plaintext
# ~/.pgpass (chmod 0600, owned by your user)
localhost:5432:*:postgres:your-strong-password

Format: hostname:port:database:username:password. With mode 0600 and matching ownership, psql uses it automatically without prompting. Useful in scripts and CI.

Check the connection string your app uses#

If the failure comes from an application rather than psql, the password often never reaches Postgres intact: a @, # or : in it breaks the URL before any authentication happens. Paste the connection string below to see how it parses — user, host, port, database — and whether the password needs percent-encoding. It runs in your browser and the password is masked in every result.

Runs entirely in your browser — nothing you paste is sent anywhere, and the password is masked in every result. Still cautious? Replace the password with any string containing the same special characters: the diagnosis only needs its shape.

Supabase local dev#

The Supabase CLI (supabase start) runs Postgres in Docker and every client connects over TCP. Common gotchas:

  • Use the exact database URL that supabase status prints — by default postgresql://postgres:postgres@127.0.0.1:54322/postgres, on port 54322, not 5432. A password copied from a hosted project will not work locally, and the reverse.
  • supabase stop keeps your local data: the CLI docs state that local data "are backed up to docker volume". Only supabase stop --no-backup deletes the data volumes. A password you changed with ALTER USER therefore survives a stop/start.

Docker (official postgres image)#

The official image applies POSTGRES_PASSWORD only when the data directory is empty: its documentation says the Docker-specific variables "will only have an effect if you start the container with a data directory that is empty" and that "any pre-existing database will be left untouched on container startup" (Docker Hub — postgres).

Two consequences:

  • Changing POSTGRES_PASSWORD in your compose file after the first start does nothing. The volume still holds the old password, so the app — now sending the new one — gets password authentication failed.
  • ALTER USER inside the container persists. The password lives in the catalog, on the volume, and survives docker compose down / up as long as the volume is kept.

The usual fix is to set the password the app expects, inside the running container:

bash
docker exec -it <container> psql -U postgres -c "ALTER USER postgres PASSWORD 'the-password-your-app-uses';"

Only if the data is disposable, reset the volume so POSTGRES_PASSWORD is applied again (this deletes every table in it):

bash
docker compose down
docker volume rm <project>_postgres-data
docker compose up -d postgres

Common mistakes#

  • Typed postgres as the user but the role is named differently — the error message names the role PostgreSQL tried, not the one you think you used. Check -U and the connection string.
  • Edited the wrong pg_hba.conf — there can be several (Debian vs Homebrew vs Docker). Confirm with SHOW hba_file; before editing.
  • Forgetting to reload — pg_hba.conf is re-read on pg_ctl reload, not on every connection. Run systemctl reload postgresql or pg_ctl reload -D ....
  • password_encryption is md5 but you switched pg_hba.conf to scram-sha-256 — every password set afterwards is stored as md5 and the SCRAM rule rejects it. Check SHOW password_encryption;, set it to scram-sha-256, then re-set the passwords.
  • Bypassing with trust — disables auth. Use scram-sha-256 and a real password.
  • An expired role — ALTER ROLE ... VALID UNTIL in the past gives the same client error; the server log says has an expired password. Fix with ALTER ROLE myrole VALID UNTIL 'infinity';.
  • .pgpass wrong permissions — psql ignores ~/.pgpass if mode is not 0600 (or 0640 with the right owner). chmod 600 ~/.pgpass.

Official references: PostgreSQL — Client Authentication (pg_hba.conf), PostgreSQL — Password Authentication, PostgreSQL — The Password File (.pgpass).

Further reading: Fix: Peer Authentication Failed for User "postgres", How to Change a PostgreSQL User Password (Supabase), Fix Deno 'UnsupportedCertVersion' TLS Error on Supabase.

Frequently Asked Questions

|

Have more questions? Contact us

Still stuck? Paste your error.

Paste the error message or stack trace — we check it against every fix and tool on the site.

Matching happens in your browser. When you search, a cleaned error signature is sent so we can spot errors we do not cover yet — never the raw text, your IP address or your e-mail. Do not paste passwords or keys. Details

Written by

Mahdi Ben Rhouma
Mahdi Ben Rhouma

Full-Stack Dev — Next.js & Supabase

Solo developer building SaaS products with Next.js and Supabase. Writing about production patterns the official docs skip.

Remote

One email a month — the latest explained errors

What the site published that month — mostly Next.js, Supabase and Node errors, explained. You confirm by email; unsubscribe any time in one click.