PostgreSQL

How to Fix "FATAL: password authentication failed for user" in PostgreSQL

4 min read by DebuggedIt

Quick answer

PostgreSQL rejects your connection attempt with a password authentication error, even when you're confident the password is correct. This error means...

PostgreSQL rejects your connection attempt with a password authentication error, even when you're confident the password is correct. This error means PostgreSQL received your connection request and actively checked your credentials against its authentication configuration β€” the fix depends on exactly which part of that check is failing.

The Problem

A normal connection attempt is rejected outright:

$ psql -U app_user -d mydb -h localhost
Password for user app_user:
psql: error: FATAL:  password authentication failed for user "app_user"

Application connections fail with the equivalent driver-level error:

FATAL: password authentication failed for user "app_user"
SQLSTATE[08006]

Why It Happens

PostgreSQL's authentication is governed by pg_hba.conf, which defines exactly which authentication method applies for each combination of database, user, and connecting host β€” and a mismatch anywhere in that chain produces this same generic error regardless of the specific underlying cause. Common causes:

  • The password is genuinely wrong, or was changed by another process without your knowledge.
  • The role (user) doesn't actually exist, which PostgreSQL deliberately reports as the same generic authentication failure rather than a more specific "role not found" message, as a security measure against username enumeration.
  • pg_hba.conf is configured with an authentication method that doesn't match how you're actually connecting β€” for example, requiring md5 or scram-sha-256 for one connection type but the client is connecting in a way that maps to a different rule entirely.
  • A recent PostgreSQL upgrade changed the default password hashing method (from md5 to scram-sha-256 starting in PostgreSQL 14), and an older client library doesn't support the newer method.
  • Environment variables or a connection string are silently overriding the password you think you're supplying interactively.

The Fix

First, confirm the role actually exists:

sudo -u postgres psql -c "\du"
                                   List of roles
 Role name |                         Attributes
-----------+------------------------------------------------------------
 app_user  | 
 postgres  | Superuser, Create role, Create DB, Replication, Bypass RLS

If it's missing, create it:

sudo -u postgres psql -c "CREATE ROLE app_user WITH LOGIN PASSWORD 'your_password';"

If the role exists but you suspect the password itself, reset it explicitly rather than guessing:

sudo -u postgres psql -c "ALTER ROLE app_user WITH PASSWORD 'new_secure_password';"

Check pg_hba.conf to confirm the authentication method matches how you're connecting:

sudo cat /etc/postgresql/16/main/pg_hba.conf | grep -v "^#"
local   all   all                     peer
host    all   all   127.0.0.1/32      scram-sha-256
host    all   all   ::1/128           scram-sha-256

The local line using peer authentication means connecting via a Unix socket requires your OS username to match the PostgreSQL role name β€” a password won't work at all for that connection type. If you need password auth locally too, either connect via TCP explicitly:

psql -U app_user -d mydb -h 127.0.0.1

Or change the local line's method to md5 or scram-sha-256 if password-based local connections are actually what you need:

local   all   all                     scram-sha-256

After any change to pg_hba.conf, reload PostgreSQL for it to take effect (a full restart isn't necessary):

sudo systemctl reload postgresql

Still Not Working?

If you recently upgraded PostgreSQL and older clients started failing, check whether the role's password hash uses the newer scram-sha-256 format that some older client libraries can't handle:

SELECT rolname, rolpassword FROM pg_authid WHERE rolname = 'app_user';

If the client library genuinely can't be upgraded, you can revert that specific role to the older md5 method as a compatibility measure, though upgrading the client is the more durable fix long-term:

SET password_encryption = 'md5';
ALTER ROLE app_user WITH PASSWORD 'your_password';

Also double-check no environment variable is silently overriding your intended password β€” PGPASSWORD takes precedence over an interactively typed password and is easy to forget you've set in a shell session:

echo $PGPASSWORD

It's also worth understanding pg_hba.conf's matching order, since PostgreSQL uses the first matching line, top to bottom, and stops there β€” it doesn't try every applicable rule and pick the best one. If you have multiple lines that could plausibly apply to your connection, an earlier, more restrictive rule can silently shadow a later, more permissive one you actually intended to use:

# This ordering means the first line always wins for local TCP connections,
# and the second line below it is effectively dead code
host    all   all   127.0.0.1/32      reject
host    all   all   127.0.0.1/32      scram-sha-256

After making any change to pg_hba.conf, it's worth reviewing the full file from top to bottom rather than just the line you edited, to confirm nothing earlier in the file is unintentionally intercepting the connection before your intended rule is ever reached. A quick way to see exactly which rule PostgreSQL is applying to a given connection attempt is checking the server log immediately after a failed attempt, since recent PostgreSQL versions log which pg_hba.conf line matched:

tail -5 /var/log/postgresql/postgresql-16-main.log