September 22, 2026 · In Ayotl
“Timeout expired”, or why the database refused to be reached
The connection string everyone copies from the dashboard works from your laptop and not from your server. The difference isn't the password.
To count each tester's active days, the brand site had to read another product's database, living in a different Supabase project. The dashboard's connection string went into the server, and the answer was always the same: `connection: timeout expired`. Not “wrong password”, not “no such host”; silence until the clock ran out.
$ dig +short A db.xxxxxxxx.supabase.co # nada
$ dig +short AAAA db.xxxxxxxx.supabase.co # 2600:1f14:131e:...
$ dig +short A aws-1-us-west-2.pooler.supabase.com
44.252.246.120 44.225.139.66 34.215.156.231From a laptop with IPv6 it works, and you conclude the string is fine. From a platform that egresses over IPv4 it doesn't resolve and just hangs. The fix is the session pooler, which changes the user and the host but not the password. Half an afternoon over a DNS record nobody shows you.
Along the way something worse and quieter showed up: the Postgres client emits an `error` event when the handshake fails, and with nobody listening Node turns it into an uncaught exception that **takes the process down**. The container died and the proxy returned a generic 502, which says nothing about the real error. Two lines fix it, and the lesson is broader: in a new integration, the first thing you write isn't the query, it's the failure handling.
And a bonus finding: while testing the other route, that project's REST API had been down for months (“could not query the database for the schema cache”) and nobody had noticed, because that app connects over plain Postgres and never uses it. A new integration is also a free audit of what you assumed was healthy.