
How to Fix a PostgreSQL Connection String That Won't Connect
PostgreSQL connection string not working? Fix connection refused, password authentication failed, no pg_hba.conf entry, SSL and Supabase pooler errors fast.
You copied the connection string straight out of your provider's dashboard (Supabase, Neon, Railway, AWS RDS, DigitalOcean) and pasted it into your app or psql, and it still won't connect. The string looks right. It came from the source of truth. And yet the terminal throws password authentication failed, or could not connect to server: Connection refused, or some cryptic Tenant or user not found you've never seen before.
Here's the thing almost every tutorial gets wrong: it assumes you control the Postgres server and can edit pg_hba.conf, restart the daemon, or sudo -i -u postgres. If you're on a managed host, you can't touch any of that. The fix lives in the connection string itself: the host, the port, the username format, the sslmode, and how special characters in your password are encoded.
This post is an error-message-indexed troubleshooter. Find the exact text your terminal printed, read the cause, apply the connection-string-level fix first. It pairs with our free PostgreSQL Connection String Validator, which parses your string in the browser and flags these issues before you deploy.
Anatomy of a PostgreSQL connection string
Almost every connection failure is a problem with one specific part of the URI. So before the error catalogue, here's the map. A standard PostgreSQL connection string looks like this:
postgresql://user:password@host:port/database?sslmode=require
Per the official libpq documentation, the scheme can be either postgresql:// or postgres://, and both are accepted and identical. Breaking it down:
| Component | Example | Notes |
|---|---|---|
| Scheme | postgresql:// | postgres:// works too |
| User | postgres | On Supabase poolers it must be postgres.<project-ref> (more below) |
| Password | p%40ssw0rd | Special characters must be percent-encoded |
| Host | db.abc.supabase.co | DNS name or IP; managed hosts often have pooler vs direct hostnames |
| Port | 5432 | Default is 5432, but DigitalOcean uses 25060, Supabase pooler 6543 |
| Database | postgres | The database name, not the project name |
| Query params | ?sslmode=require | TLS mode, plus pgbouncer=true, channel_binding=require, etc. |
libpq is explicit that "the connection URI needs to be encoded with percent-encoding if it includes symbols with special meaning in any of its parts." That single rule causes a large share of "wrong password" errors that aren't actually wrong passwords. Keep this table handy, since every fix below maps back to one of these fields.
The most common reasons your connection string won't connect
A quick triage trick before the details: the error message tells you which layer failed. could not translate host name is DNS, before any TCP. Connection refused means TCP reached the host but nothing is listening. no pg_hba.conf entry and password authentication failed mean you reached Postgres and it's talking to you, so it's auth/config, not network. server does not support SSL is a TLS-layer mismatch. Matching the message to the layer saves hours. The six sections below cover the most frequent failures; the quick-reference table at the end adds one more (node-postgres self-signed certificates).
1. password authentication failed
FATAL: password authentication failed for user "andym"
Cause. You reached the server and it's willing to talk, but it rejected your credentials. The obvious culprit is a wrong username or password. The non-obvious one, and the reason this error shows up even when you know the password is right, is special characters in the password that aren't percent-encoded inside the URI. The parser misreads or truncates the password at the first reserved character.
A # is especially nasty: in URI syntax it begins a fragment. A strict parser rejects the whole string (node-postgres throws Invalid URL); a lax one drops everything after the # from the password. Either way the password that reaches the server is wrong, with little hint why, so encode it as %23.
Fix. Percent-encode the reserved characters in the password. The ones that actually break parsing are @, /, ?, #, and % (plus :, which otherwise splits the username from the password); the others below are optional but harmless to encode:
| Char | Encoded | Char | Encoded |
|---|---|---|---|
@ | %40 | ? | %3F |
: | %3A | & | %26 |
/ | %2F | space | %20 |
# | %23 | $ | %24 |
% | %25 | = | %3D |
[ | %5B | ] | %5D |
A password of p@$$w0rd needs the @ encoded at minimum (so p%40$$w0rd already connects, since $ is legal unencoded), and the fully encoded p%40%24%24w0rd works just as well:
# Broken: the second @ is read as the user/host separator, splitting the password
postgresql://postgres:p@$$w0rd@db.abc.supabase.co:5432/postgres
# Fixed: password percent-encoded
postgresql://postgres:p%40%24%24w0rd@db.abc.supabase.co:5432/postgres
When in doubt, encode the whole password before pasting it. If this is the error you keep hitting, drop the string into the connection string validator, which flags unencoded characters instantly.
2. could not translate host name
could not translate host name "db.abc.supabase.co" to address: Name or service not known
Cause. DNS resolution failed: your OS couldn't turn the hostname into any usable IP address. Usually a typo in the host. But there's a managed-host trap: Supabase's direct connection host (db.<ref>.supabase.co) resolves only to IPv6 by default. On an IPv4-only network or runtime (most serverless platforms like Vercel, Lambda, and Cloudflare, plus plenty of corporate and home networks), no usable address comes back. Depending on your resolver, a pure IPv6 resolution may instead surface later as Network is unreachable. Either way, the fix is the same. (The Name or service not known suffix is the Linux/glibc wording; macOS prints nodename nor servname provided, or not known, and Docker containers often print Temporary failure in name resolution for the same DNS failure.)
Fix. If it's a typo, fix the host. For Supabase on IPv4-only networks, switch to a pooler hostname, which is IPv4 across all tiers, or buy the IPv4 add-on for the direct connection:
# Direct (IPv6-only by default): fails on IPv4-only runtimes
postgresql://postgres:pw@db.abc.supabase.co:5432/postgres
# Session pooler (IPv4): note the postgres.<ref> username
postgresql://postgres.abc:pw@aws-0-eu-west-1.pooler.supabase.com:5432/postgres
Be careful with nslookup here: a plain nslookup db.abc.supabase.co (or ping) will often return an IPv6 (AAAA) record and look perfectly healthy even though an IPv4-only stack can't use it. Check specifically for an A record (nslookup -type=A db.abc.supabase.co), and treat "resolves to IPv6 only, no IPv4 answer" as the real signal. For an end-to-end test, psql -h <host> from the same environment your app runs in. See the pooler section below for the full breakdown.
3. no pg_hba.conf entry ... no encryption
FATAL: no pg_hba.conf entry for host "123.123.123.123", user "andym", database "testdb", no encryption
Cause. You reached the server, but no matching rule in pg_hba.conf allows your connection. The trailing no encryption describes your side: your client connected in plaintext (that wording is PostgreSQL 12+; pre-12 said SSL off). The usual reason no rule matches a plaintext attempt is that the rule that would match is hostssl (TLS-only), so the practical fix is almost always the same: turn on TLS. The exact trailing wording is server- and version-dependent, and the canonical doc example shows just the bare no pg_hba.conf entry for host …, user …, database … line.
Fix. Add ?sslmode=require to the connection string:
# Broken: no TLS, rejected by an SSL-enforcing host
postgresql://user:pw@host:5432/mydb
# Fixed: request TLS
postgresql://user:pw@host:5432/mydb?sslmode=require
Managed hosts enforce SSL by default: Neon rejects all non-TLS connections, DigitalOcean always enforces SSL, and AWS RDS enforces it via the rds.force_ssl parameter, whose default is version-dependent: 1 (on) for RDS for PostgreSQL 15 and later, 0 (off) for 14 and older. So a PostgreSQL 15+ instance on a default parameter group rejects plaintext out of the box, while 14-and-older accepts it until you set rds.force_ssl=1 yourself.
4. server does not support SSL, but SSL was required
error: server does not support SSL, but SSL was required
Cause. The mirror image of #3. Your client demanded SSL (sslmode=require or higher), but the server doesn't have it enabled. This is classic with a default local PostgreSQL build or a bare docker postgres image that ships without ssl=on.
Fix. For a trusted local server, just connect without TLS by dropping sslmode=require or setting sslmode=disable:
# Broken against a local server with no SSL configured
postgresql://postgres:pw@localhost:5432/mydb?sslmode=require
# Fixed: local, trusted network, no TLS
postgresql://postgres:pw@localhost:5432/mydb?sslmode=disable
If it's not a local box, the real fix is to enable SSL on the server (ssl=on plus a cert and key) rather than disabling it. Never use sslmode=disable against a database reachable over the public internet.
5. could not connect to server: Connection refused
could not connect to server: Connection refused
Is the server running on host "host" and accepting
TCP/IP connections on port 5432?
Cause. Nothing is listening at that host and port; the TCP connection was actively rejected. The server is down, listening on a different port, not configured for TCP/IP, or a firewall / security group is blocking the port. On managed hosts, the single most common cause is the wrong port. (This is the PostgreSQL 11-and-earlier wording, still emitted by many third-party drivers; modern libpq prints connection to server at "host" (IP), port 5432 failed: Connection refused followed by Is the server running on that host and accepting TCP/IP connections?.)
Fix. Verify the port first, because managed providers don't all use 5432:
# DigitalOcean managed Postgres: port 25060, NOT 5432
postgresql://doadmin:pw@db.ondigitalocean.com:25060/defaultdb?sslmode=require
# Supabase transaction pooler: port 6543
postgresql://postgres.abc:pw@aws-0-eu-west-1.pooler.supabase.com:6543/postgres
Then confirm the server is reachable with pg_isready -h <host> -p <port>, and on AWS RDS or a self-hosted box, check the firewall / security group allows inbound on that port. If you're on a managed host, also check the project isn't paused: Supabase free-tier projects pause after inactivity and refuse connections until you resume them.
6. Tenant or user not found (Supabase pooler)
FATAL: Tenant or user not found
Cause. You're connecting to the Supabase pooler (aws-<region>.pooler.supabase.com) with the plain username postgres. Supabase's pooler (Supavisor) parses your project reference out of the username to route to the right tenant. Without it, there's no tenant to route to.
Fix. Use the tenant-qualified username postgres.<project-ref>:
# Broken: plain "postgres" against the pooler
postgresql://postgres:pw@aws-0-eu-west-1.pooler.supabase.com:6543/postgres
# Fixed: username is postgres.<project-ref>
postgresql://postgres.your-project-ref:pw@aws-0-eu-west-1.pooler.supabase.com:6543/postgres
The Supabase docs put it plainly: "If the username is postgres the username you use for Supavisor is postgres.[PROJECT_REF]." The plain postgres username only works on the direct connection (db.<ref>.supabase.co), never the pooler.
Quick reference: error → cause → fix
| Error message | Layer | Root cause | Connection-string fix |
|---|---|---|---|
password authentication failed for user | Auth | Wrong creds, or unencoded special chars in password | Percent-encode the password (@→%40, #→%23, /→%2F) |
could not translate host name | DNS | Typo, or Supabase IPv6 direct host on IPv4 network | Fix host, or use the IPv4 pooler hostname |
no pg_hba.conf entry ... no encryption | Auth/TLS | Server requires SSL, client connected plaintext | Add ?sslmode=require |
server does not support SSL, but SSL required | TLS | Client demands SSL, server has none (local/Docker) | ?sslmode=disable for trusted local servers |
Connection refused | TCP | Wrong port, server down, firewall, or paused project | Fix the port (DO 25060, Supabase pooler 6543); check firewall |
Tenant or user not found | Auth/routing | Plain postgres user against Supabase pooler | Use postgres.<project-ref> as the username |
self signed certificate in certificate chain | TLS | node-postgres verifies CA strictly by default | ssl: { rejectUnauthorized: false } or supply the CA |
Pooler vs direct connection (the Supabase gotcha)
Supabase trips up more developers than any other host here, because it exposes three different endpoints for the same database, and they have different hostnames, ports, IP stacks, and username rules. Choosing the wrong one produces exactly the could not translate host name, Connection refused, and Tenant or user not found errors above.
| Endpoint | Hostname & port | IP stack | Username | Best for |
|---|---|---|---|---|
| Direct | db.<ref>.supabase.co:5432 | IPv6 (IPv4 add-on) | postgres | Persistent VMs/containers, migrations, pg_dump |
| Session pooler | aws-<region>.pooler.supabase.com:5432 | IPv4 (all tiers) | postgres.<ref> | Persistent backends on IPv4-only networks |
| Transaction pooler | aws-<region>.pooler.supabase.com:6543 | IPv4 (all tiers) | postgres.<ref> | Serverless / edge functions, many short-lived connections |
Three rules that save you:
- The pooler username is always
postgres.<project-ref>, never plainpostgres. The direct connection is the opposite. - Ports 5432 (session) and 6543 (transaction) share the same pooler host. Connections are pooled across both. If you're serverless, use 6543.
- The transaction pooler does not support prepared statements. Per the Supabase docs, Prisma needs
?pgbouncer=trueadded to the string (which disables prepared statements), and for serverless you'll usually also setconnection_limit=1:
# Prisma against the Supabase transaction pooler
postgresql://postgres.abc:pw@aws-0-eu-west-1.pooler.supabase.com:6543/postgres?pgbouncer=true&connection_limit=1
One more node-specific gotcha worth knowing: with node-postgres, sslmode=require is treated as an alias for verify-full, so it does full certificate verification (unlike libpq, which only encrypts without verifying the CA). That's still the default in the current release line (pg v8 / pg-connection-string v2), not a thing of the past; it only relaxes to libpq's encrypt-only behavior in the unreleased pg v9. Against managed hosts whose chain Node doesn't trust, it throws self signed certificate in certificate chain (hyphenated as self-signed … on Node 17+, which bundles OpenSSL 3). The pragmatic fix is ssl: { rejectUnauthorized: false } when you're not validating the CA, or supply the provider's CA via ssl: { ca }; you can also opt into libpq behavior now with ?sslmode=require&uselibpqcompat=true. Also: if you put sslmode (or sslcert/sslkey/sslrootcert) in the connection string, pg replaces your entire ssl config object, so don't mix the two.
Test your connection string before you deploy
Most of these errors are findable before you ship, by reading the string carefully, which is exactly the kind of tedious parsing a tool should do for you. Our free PostgreSQL Connection String Validator parses your string in the browser, breaks it into its components, and flags the common problems: unencoded special characters in the password, a missing sslmode on a host that needs it, a Supabase pooler host paired with a plain postgres username, a non-standard port, and more.
It runs entirely client-side. Your credentials never leave your machine; nothing is sent to a server, logged, or stored. Validate the string, fix whatever it flags using the sections above, and you'll catch most failures before the first deploy instead of after.
Skip connection strings entirely
Step back for a second. Every error in this post (encoding, host stack, port, SSL mode, pooler username) exists because you're hand-assembling and pasting a connection string in the first place. Remove that step and the whole class of bugs disappears.
That's the case for connecting via OAuth instead. With Supabase OAuth, Codeless Sync authorizes against your Supabase project directly, so there's no string to URL-encode, no pooler-vs-direct decision to get wrong, no sslmode to remember, and no long-lived password living in an env var. CLS validates and tests the connection at the moment you connect a database, so a misconfiguration surfaces immediately instead of three deploys later. Connect once, and your Stripe, QuickBooks, Xero, or Paddle data keeps syncing to Postgres without you ever re-pasting a string.
If you're still choosing where to host your Postgres, Supabase vs Neon vs Railway for SaaS compares the connection-string ergonomics alongside pricing and scaling. The full setup walkthrough lives in the database setup guide.
Frequently Asked Questions
How do I fix "password authentication failed for user postgres"?
First confirm the username and password are correct. If they are, the cause is almost always special characters in the password that aren't percent-encoded inside the connection string. Characters like @, :, /, ?, and # have special meaning in a URI, so the parser misreads or splits the password at them; a # in particular makes a strict parser reject the string and a lax one drop everything after it. Encode them: @→%40, /→%2F, ?→%3F, #→%23. So p@$$w0rd becomes p%40%24%24w0rd (encoding the @ is what matters; encoding the $ too is just harmless). The Codeless Sync connection string validator flags unencoded characters automatically.
Why can't I connect to my Supabase database?
The three usual causes are all in the connection string. Tenant or user not found means you used plain postgres against the pooler, so switch the username to postgres.<project-ref>. could not translate host name on a serverless or IPv4-only network means you're using the IPv6-only direct host (db.<ref>.supabase.co), so switch to the IPv4 pooler (aws-<region>.pooler.supabase.com). And Connection refused often means your free-tier project is paused (resume it in the dashboard) or you used the wrong port.
What does "no pg_hba.conf entry for host" mean and how do I fix it?
It means you reached the Postgres server, but no rule in its host-based authentication config (pg_hba.conf) allows your connection. When the message ends in no encryption or SSL off, that part describes your connection (it was plaintext), and the rule that would have matched is almost always hostssl (TLS-only). On a managed host you can't edit pg_hba.conf, but you don't need to: just add ?sslmode=require to your connection string. Neon, DigitalOcean, and SSL-enforcing AWS RDS instances all require this.
How do I fix "could not connect to server: connection refused" in PostgreSQL?
Connection refused means the TCP connection reached the host but nothing was listening on that port. On managed hosts the most common cause is the wrong port: DigitalOcean uses 25060, the Supabase transaction pooler uses 6543, not the default 5432. Check the exact port in your provider's dashboard. Other causes are a paused project, a firewall or security group blocking the port, or the server being down. Verify reachability with pg_isready -h <host> -p <port>.
What causes the Supabase "Tenant or user not found" error?
It's caused by connecting to the Supabase pooler with the plain username postgres. The pooler (Supavisor) reads your project reference from the username to route to the correct tenant, so it needs the form postgres.<project-ref>, for example postgres.your-project-ref. Plain postgres only works on the direct connection (db.<ref>.supabase.co), not the pooler host (aws-<region>.pooler.supabase.com).
Do I need sslmode=require in my PostgreSQL connection string?
For most managed hosts, yes: Neon, DigitalOcean, and SSL-enforcing AWS RDS reject plaintext connections, so omitting it gives no pg_hba.conf entry ... no encryption. (Supabase is the exception: it accepts non-SSL connections by default for client compatibility and makes SSL enforcement opt-in, though sslmode=require is still good practice there.) For a trusted local server with no SSL configured, do the opposite and use sslmode=disable, or you'll get server does not support SSL, but SSL was required. Be aware that in libpq, require encrypts but does not verify the server's certificate; for genuine protection against man-in-the-middle attacks, use verify-full with the provider's CA certificate. (Some drivers differ: node-postgres treats require as full verification, as noted above.)
How do I put a special-character password in a PostgreSQL connection string?
Percent-encode every reserved character in the password before placing it in the URI. The key ones: @→%40, :→%3A, /→%2F, ?→%3F, #→%23, &→%26, =→%3D, space→%20, $→%24, %→%25, [→%5B, ]→%5D. When unsure, encode the entire password. The alternative is to avoid the URI form entirely and pass host, port, user, and password as separate parameters, which skips URI parsing rules altogether.
Related:
Questions or feedback? Feel free to reach out. If you found this helpful, you can try Codeless Sync for free.