Postgres 'password authentication failed' — fix the credentials
Quick answer: Postgres rejected the credentials your app sent. The usual causes are a wrong password in DATABASE_URL, special characters in the password that were not URL-encoded (@ : / # break the URL), or the env var not loading. Test the exact credentials with psql; if that works, the bug is in how your app builds the connection string.
What the error looks like
From the server log or the client - it names the user that was rejected:
FATAL: password authentication failed for user "app"
# from a Node client (pg):
error: password authentication failed for user "app"
at Parser.parseErrorMessage (.../pg-protocol/...)
code: '28P01'
SQLSTATE 28P01 is "invalid_password." The credentials reached Postgres and were refused - so the host and port are fine; the username or password is wrong, or not encoded the way you think.
Why it happens
Wrong password or user
The password in DATABASE_URL is wrong, or you are connecting as the wrong user.
Special chars not URL-encoded
A password with @ : / or # breaks the URL, so the parsed password is wrong.
Env var did not load
.env was not read or the variable name is wrong, so an empty or default password is sent.
pg_hba.conf / auth method
The required method (scram-sha-256 vs md5, or password vs trust) does not match how the client connects.
Diagnose it in three steps
Test the exact credentials with psql
psql "postgresql://app:thepassword@db:5432/myapp"
# works? the creds are fine - the bug is how your app builds the URL.
# fails? the username or password is genuinely wrong.Print what your app actually sends
const u = new URL(process.env.DATABASE_URL);
console.log('user:', u.username, 'pass length:', u.password.length);
// length 0 = env var not loaded; wrong length = bad encoding.Check for special characters
# if the password has @ : / # space, it MUST be URL-encoded in a URL:
# p@ss/word -> p%40ss%2FwordCorrect, encode, or pass discrete fields
Set the right user and password, and if you use a DATABASE_URL, URL-encode any special characters. The most robust option is to skip the URL and pass discrete connection fields, which removes encoding mistakes entirely.
# URL form - encode special characters:
DATABASE_URL=postgresql://app:p%40ss%2Fword@db:5432/myapp
// or discrete fields (no URL-encoding needed):
const { Pool } = require('pg');
const pool = new Pool({
user: 'app',
password: process.env.PGPASSWORD, // raw value, no encoding
host: 'db', port: 5432, database: 'myapp',
});
# if pg_hba.conf is the issue, align the method and reload:
# host myapp app 0.0.0.0/0 scram-sha-256
sudo systemctl reload postgresql
After changing a role's password with ALTER USER app WITH PASSWORD '...', make sure every instance uses the new value - a stale password on one node looks like an intermittent auth failure.
Consistent DB credentials across your fleet
Intermittent auth failures are usually a config-consistency problem - one instance has a stale or mis-encoded credential. On your own servers, Infraveil distributes database credentials and config consistently across the services you run, so every instance connects with the same value, secret rotation is coordinated, and changes are approval-gated and recorded.
Frequently asked questions
Why does psql work but my app fails?
The app builds the connection string differently - most often a special character in the password not URL-encoded in DATABASE_URL, or the env var did not load. Print the exact string the app uses (password masked) and compare.
How do I handle special characters in the password?
URL-encode them in a DATABASE_URL: @ becomes %40, : becomes %3A, / becomes %2F, # becomes %23. Or pass user and password as discrete fields to the client to avoid encoding entirely.
What is pg_hba.conf's role?
It controls which auth method Postgres requires per host and user. A mismatch (scram-sha-256 vs md5, or password vs none) fails auth even with the right password. Align it with how the app connects and reload Postgres.
Could it be the wrong user, not the password?
Yes - the error names the rejected user. Confirm that user exists, has a password set, and has privileges on the target database. Connecting as the wrong user is a common cause.
Client-side, no signup — they run in your browser.