Fix: Sequelize authenticate() Hangs With No Success or Error
Your container logs "start connect" and then nothing — no success, no error. The connection is not failing silently; it is still trying, because pg never times out by default.
A container running a Node + Sequelize API prints start connect and then stops — no
Connection has been established successfully, no Unable to connect to the database, nothing.
The try/catch around sequelize.authenticate() never fires either branch, even though the
exact same code connects fine outside Docker. This is the symptom behind a widely viewed Stack
Overflow question, "Sequelize.authenticate() do not working (no success or error response) for
google cloud sql connection inside Docker"
(17 votes, over 10,800 views).
TL;DR#
sequelize.authenticate() is not failing silently — it is still running. Sequelize's Postgres
dialect uses pg, and pg defaults connectionTimeoutMillis to
0, which means no timeout at all: if the TCP connection to the database never completes
(wrong host, no route from the container to a Cloud SQL private IP, a typo'd Compose service
name), the connect() call just keeps waiting instead of rejecting. Give it an explicit
connectionTimeoutMillis so a dead connection fails fast with a real error you can log and act
on, then fix why the container cannot reach the database in the first place.
api_1 | Init db
api_1 | start connect
api_1 | Server running in development mode on port 5000No further line ever appears — not an error, not a success message.
Why it happens#
Sequelize's Postgres connection goes through pg, which builds its connection options in
connection-parameters.js:
if (config.connectionTimeoutMillis === undefined) {
this.connect_timeout = process.env.PGCONNECT_TIMEOUT || 0
} else {
this.connect_timeout = Math.floor(config.connectionTimeoutMillis / 1000)
}and in client.js
the connection-timeout timer is only armed when that value is greater than zero:
this._connectionTimeoutMillis = c.connectionTimeoutMillis || 0
// …
if (this._connectionTimeoutMillis > 0) {
this.connectionTimeoutHandle = setTimeout(() => {
con._ending = true
con.stream.destroy(new Error('timeout expired'))
}, this._connectionTimeoutMillis)
}With the default of 0, that setTimeout is never scheduled. pg then relies entirely on
whatever the underlying TCP stack eventually reports — and how long that takes, or whether it
reports anything at all, depends on what is on the other end of the route: a host that actively
refuses the connection answers fast with ECONNREFUSED; a firewall, security group or missing
route that drops packets silently can leave the OS retrying for a long time before it gives up,
if it gives up before the process itself is killed. Until one of those happens, the connect()
promise is simply pending: not resolved, not rejected, just waiting for a reply that may or may
not be coming back over the wire. That is exactly the "no success or error response" reported in
the question — the question's own body says only that "everythings works fine in my local
development", without saying what, specifically, happened differently there; the common
explanation in cases like this is that outside Docker the connection either succeeds or fails
immediately, while inside the docker-compose network the path to the target — a Google Cloud
SQL instance's private IP, in the original question — may not get any response at all.
Reproduced (Node 20, pg 8.11.3, official node:20-alpine image, 8 October 2026): a pg
client connecting to a non-routable test address with the default options logged nothing for
over 8 seconds — still pending, no success, no error — and only reported ECONNREFUSED after
21 seconds, once the container's own network stack gave up on that unreachable route. The same
client with connectionTimeoutMillis: 2000 reported timeout expired in 2 seconds flat. One
option changed, nothing else — and a different network path (one that drops packets instead of
answering) can take even longer than 21 seconds to resolve on its own, or never resolve at all,
which is the whole reason the explicit timeout matters.
Fix#
1. Set an explicit connection timeout. Pass it through Sequelize's dialectOptions (it is
forwarded verbatim to pg):
this._sequelize = new Sequelize({
dialect: 'postgres',
database: this.config[this.env].database,
username: this.config[this.env].username,
password: this.config[this.env].password,
host: this.config[this.env].host,
dialectOptions: {
connectionTimeoutMillis: 2000,
},
});Now a dead connection rejects in 2 seconds with Error: timeout expired instead of hanging
indefinitely — the catch block in the original question's connect() method finally has
something to log.
2. Read the error that timeout now surfaces, and fix the actual route. For the Google Cloud
SQL case in the question, a container cannot reach a Cloud SQL instance's private IP directly
unless it is on the same VPC with private services access configured; the supported path from a
container is the Cloud SQL Auth
Proxy sidecar, connecting over a
Unix socket or the container's own loopback address instead of the instance's own IP. If the
host in dbConfig is the Cloud SQL instance's private IP and there is no proxy or VPC peering,
that is the unreachable route the timeout now reports.
3. For a docker-compose service, double-check the host is the service name, not the loopback
address. Inside a container, the loopback address means the container itself; the database is
only reachable at the Compose service name (e.g. host: 'db'), as described for the closely
related ECONNREFUSED failure mode on this site.
4. If the error becomes Please install pg package manually, that is Sequelize's own
dialect loader throwing when the pg package is not resolvable from node_modules — it is
listed as an optional peer dependency, not a direct one, in older Sequelize majors. Add it to
package.json explicitly:
npm install pgThat is a separate failure from the hang above: it throws immediately and synchronously, before
any connection attempt, rather than leaving authenticate() pending.
Verify the fix#
Rebuild and run the stack with the timeout in place, and deliberately point host at an address
the container cannot reach (as in the repro above):
docker compose up --buildExpect a line within the configured connectionTimeoutMillis, not after a long silence:
Unable to connect to the database: Error: timeout expiredThen point host back at the real, reachable database (the Compose service name, or the Cloud
SQL Auth Proxy's socket), rebuild once more, and confirm the success path logs promptly instead,
without the earlier long silence:
Connection has been established successfully.If the error now changes to connect ECONNREFUSED at a specific IP and port, the route exists
but nothing is listening there yet — continue with fixing ECONNREFUSED in Docker
Compose, which covers the
loopback-address-inside-a-container trap directly. If it instead becomes bind: address already in use, that is a different, host-side port conflict covered in fixing Postgres 5432 "address
already in use" in Docker.
One email a month — the latest explained errors
What the site published that month — mostly Next.js, Supabase and Node errors, explained. You confirm by email; unsubscribe any time in one click.
A variant: Prisma instead of Sequelize#
Prisma's own PostgreSQL connector has long accepted a connect_timeout connection-string
parameter with a short, non-zero default — an explicit, fast PrismaClientInitializationError P1001 ("Can't reach database server") rather than a hang, which is the behaviour behind Prisma:
Can't reach database server at database:5432 on M1.
Prisma's own PostgreSQL connector
reference
documents a change on the newer driver-adapter path
(PrismaPg, PrismaNeon, and the rest): it switched to using pg's own connection defaults
instead, which includes that same un-timed default — so the exact hang described above can now
surface one layer up the stack as well, unless an explicit connectionTimeoutMillis is set on
the adapter. The driver and the error message differ; the underlying lesson, that "no error yet"
is not the same as "no error ever", does not.
A working Docker Compose development setup for a Node API — healthchecks, depends_on, and the
bind-mount layout that avoids node_modules clobbering — is covered end to end in the Docker
dev environment setup guide; wiring a
depends_on: condition: service_healthy there removes most of the race that leads to this hang
in the first place, by not starting the API container until Postgres already accepts
connections.
Frequently Asked Questions
Still stuck? Paste your error.
Paste the error message or stack trace — we check it against every fix and tool on the site.
Continue Reading
Fix ECONNREFUSED 127.0.0.1:5432 in Docker Compose (Node)
Your Node app and Postgres both start, yet every query fails with connection refused. Read the address in the error, fix the host, and gate startup on pg_isready.
PostgreSQL 18 Docker: Fix Existing Volume Startup Errors
Separate a Docker mount failure from a PostgreSQL major upgrade, preserve the original volume and prove the restored database survives a restart.
Fix Deno 'UnsupportedCertVersion' TLS Error on Supabase
Deno Deploy throws 'UnsupportedCertVersion' TLS error when connecting to Supabase Postgres. Here's how to use --unsafely-ignore-certificate-errors and verify your connection stays secure.


