connectionSQLSTATE 28P01

password authentication failed for user "postgres"

What it means

This Postgres error (SQLSTATE 28P01) means the server rejected your credentials before opening the connection. On Supabase it is almost always a wrong DB password, the wrong username on the pooler host (it must be postgres.<project-ref>, not postgres), or a password with special characters that was not URL-encoded in the connection URI.

Why it happens

28P01 is raised by Postgres during the authentication handshake, before any query runs. The server received a username/password pair it does not accept. In a Supabase migration there are three concrete causes.

  1. Wrong database password. The DB password is not your Supabase account password and not shown after project creation. If you never saved it, you do not have it — you have to reset it.

  2. Direct-connection username used on the pooler host. Supabase exposes two endpoints. The direct connection (db.<project-ref>.supabase.co:5432) uses the username postgres. The connection pooler (...pooler.supabase.com:6543 or :5432) requires the tenant-qualified username postgres.<project-ref>. Send postgres to the pooler and it rejects the login with 28P01, even though the password is correct.

  3. A password with special characters, not URL-encoded. When the password is embedded in a URI like postgresql://postgres:p@ss/w0rd@host:5432/postgres, the raw @ and / are parsed as URI delimiters. The client sends a truncated password and the server rejects it.

How to fix it

First, confirm which endpoint you are hitting. The port tells you: 5432 on db.<ref>.supabase.co is direct; 6543 (or 5432) on *.pooler.supabase.com is the pooler.

Use the correct username for the host. On the pooler, the username must include the project ref:

# Direct connection (port 5432, db.<ref>.supabase.co)
postgresql://postgres:[PASSWORD]@db.abcdefghijklmno.supabase.co:5432/postgres

# Pooler (port 6543, aws-0-<region>.pooler.supabase.com)
postgresql://postgres.abcdefghijklmno:[PASSWORD]@aws-0-eu-central-1.pooler.supabase.com:6543/postgres

URL-encode reserved characters in the password. If the password is p@ss:w0rd/x, encode it before putting it in the URI:

CharEncoded
@%40
:%3A
/%2F
?%3F
#%23
&%26

So p@ss:w0rd/x becomes p%40ss%3Aw0rd%2Fx:

postgresql://postgres:p%40ss%3Aw0rd%2Fx@db.abcdefghijklmno.supabase.co:5432/postgres

The connection string builder does this encoding for you and picks the right username per host.

Reset the password if you don't have it. In the Supabase dashboard: Settings → Database → Database password → Reset. Choose a password without reserved characters to avoid the encoding step entirely. Then test the new credentials with psql:

psql "postgresql://postgres:[NEW_PASSWORD]@db.abcdefghijklmno.supabase.co:5432/postgres" -c "select current_user, current_database();"

A clean login returns:

 current_user | current_database
--------------+------------------
 postgres     | postgres

If that still fails with 28P01, the password is wrong — reset again and copy it exactly, with no leading or trailing whitespace.

How to prevent it

  • Store the password the moment you reset it. Supabase does not display it again. Keep it in a secret manager, not in a URI in your shell history.
  • Prefer a password without reserved characters (@ : / ? # & %). It removes the entire class of URL-encoding bugs.
  • Match username to host every time. Pooler → postgres.<project-ref>. Direct → postgres. When in doubt, copy the exact string from Settings → Database → Connection string and pick the tab for the endpoint you want.
  • Test connectivity with a trivial query first (select 1;) before running a migration. A 28P01 during batched inserts wastes far more time than catching it up front. If the connection succeeds but a later step fails on IPv6 or connection limits, see ipv6-enetunreach and too-many-connections.

SupaMigrate validates both the source and destination connection strings — username, host, and password encoding — before it starts the migration, so 28P01 surfaces at connect time rather than mid-run.

Frequently asked questions

Why do I get 28P01 only on the pooler but not on the direct connection?
The pooler (Supavisor / port 6543) requires the tenant-qualified username postgres.<project-ref>. The direct host (db.<ref>.supabase.co, port 5432) uses plain postgres. Same password, different username. Using postgres on the pooler fails authentication.
My password is correct but it still fails. What else could it be?
If the password contains @, :, /, ?, #, or & and you put it inside a connection URI, those characters break parsing. URL-encode them: @ becomes %40, : becomes %3A, / becomes %2F. Or reset the password to one without reserved characters.
Does resetting the database password log out my app users?
No. The database password is separate from Supabase Auth. Resetting it in Settings → Database changes the Postgres role password only; your application end users and their sessions are unaffected.

Related errors