MongoDB connection refused (tested)

MongoDB connection errors in Node almost always surface as a MongoServerSelectionError, not a plain ECONNREFUSED. The driver hides the instant OS-level refusal behind its own retry loop and a 30-second default timeout. We ran a real MongoDB in Docker, then measured the closed-port error, the real wall-clock wait, mongoose's wrapper, and what happens to in-flight queries when a live server disappears mid-session.

1. The error you are probably seeing

With the official mongodb driver (measured, v7.7.0), connecting to a port nothing is listening on throws a MongoServerSelectionError, not a raw ECONNREFUSED. We connected to port 27019 (nothing listening) and let the driver run to its default timeout:

MongoServerSelectionError: connect ECONNREFUSED 127.0.0.1:27019
    at Topology.selectServer (.../mongodb/lib/sdam/topology.js:348:38)
    at async Topology._connect (.../mongodb/lib/sdam/topology.js:220:28)
    at async MongoClient.connect (.../mongodb/lib/mongo_client.js:195:13)
{
  errorLabelSet: Set(3) { 'SystemOverloadedError', 'RetryableError', 'ResetPool' },
  [cause]: Error: connect ECONNREFUSED 127.0.0.1:27019 {
    errno: -111, code: 'ECONNREFUSED', syscall: 'connect'
  }
}

The real ECONNREFUSED is still there — it is the .cause of the error, and it fires in a few milliseconds, same as Postgres and Redis. But the driver does not throw it to your code right away. It treats the first refusal as "this one server is bad" and keeps retrying server selection against the whole topology until serverSelectionTimeoutMS runs out, which defaults to 30000. Only then does it give up and throw MongoServerSelectionError, with the original ECONNREFUSED attached as .cause.

2. Our test setup

This box has Docker but no mongod or mongosh binaries (which mongod mongosh returned nothing). We ran a real, throwaway MongoDB and drove it with Node instead of the Mongo shell.

$ docker run --rm -d --name forever-mongotest -p 27018:27017 mongo:7
$ docker logs forever-mongotest | tail -1
{"msg":"Waiting for connections","attr":{"port":27017,"ssl":"off"}}

mongod was already "Waiting for connections" by the time the pull finished and the container started (image was cached after the first pull). We connected the mongodb driver to the live container, to a closed port (27019), then stopped the live container mid-session with docker stop and started a fresh one on the same port 5 seconds later, with no reconnect code in the app.

3. Results: a 30-second default wait, confirmed by the clock

ScenarioWhat we didExact resultWall-clock time
Happy pathConnected to live mongo:7 on 27018Connected, buildInfo returned version 7.0.4326 ms
Closed port, default optionsConnected to port 27019, no optionsMongoServerSelectionError: connect ECONNREFUSED 127.0.0.1:2701930007 ms
Closed port, serverSelectionTimeoutMS: 2000Same, with an explicit 2 s capSame error message, same error class2011 ms
Closed port, mongoose defaultmongoose.connect() with serverSelectionTimeoutMS: 2000MongooseServerSelectionError: connect ECONNREFUSED 127.0.0.1:270192016 ms

The first row proves this is not a hang on our end: a reachable MongoDB answers in milliseconds, same as Postgres or Redis. The second row is the one that catches people out — with no options set, our code sat for exactly 30 seconds before seeing any error at all, even though the OS refused the TCP connection in under 5 ms. We confirmed the internal retries are frequent, not one 30 s sleep: listening for serverHeartbeatFailed events during the 2 s run showed attempts at roughly 12 ms, 516 ms, 1018 ms and 1522 ms — about every 500 ms, which matches the driver's minHeartbeatFrequencyMS default used while establishing a connection.

4. mongoose wraps the same error, doesn't fix the wait

Most apps use mongoose, not the raw driver. We measured that mongoose's error has a different name, MongooseServerSelectionError instead of MongoServerSelectionError, but the same message and the same underlying 30 s default (we tested with an explicit 2 s cap for speed; the message text was identical: connect ECONNREFUSED 127.0.0.1:27019). If your code checks err.name or does err instanceof MongoServerSelectionError, that check will miss on mongoose — use err.name === 'MongooseServerSelectionError' or check for the driver error as err.cause/nested reason instead, or just match on the ECONNREFUSED substring in the message.

5. Live test: stop the server, watch queries queue, restart it

We connected to the live container, pinged it once a second, then ran docker stop forever-mongotest about 6.8 s in and started a fresh container on the same port roughly 5 s after that, with serverSelectionTimeoutMS: 20000 and no reconnect code.

[+6534ms] heartbeat ok
[+6810ms] heartbeat FAILED interrupted at shutdown
[+7315ms] heartbeat FAILED connect ECONNREFUSED 127.0.0.1:27018
[+7819ms .. +12344ms] heartbeat FAILED connect ECONNREFUSED 127.0.0.1:27018   (every ~500ms)
[+12846ms] heartbeat FAILED connection <monitor> to 127.0.0.1:27018 closed
[+13852ms] heartbeat ok
[+13859ms] ping -> OK   (6 pings issued during the outage all resolved here, together)

Two things stand out. First, the driver recovered with zero reconnect code, the same as what we measured for ioredis against Redis — it kept retrying heartbeats roughly every 500 ms and picked the new server up the moment it started listening. Second, and different from both Postgres and Redis: the ping commands we issued during the outage did not reject or error individually. They stayed pending — queued behind server selection — and all resolved successfully together the instant the server came back, because the whole outage (about 7 s) was inside the 20 s serverSelectionTimeoutMS we'd set. An app that only logs on error would see nothing at all for an outage shorter than its timeout, just slower responses.

6. Common real causes

7. How MongoDB compares to Postgres and Redis

We measured ECONNREFUSED behaviour the same way for all three database layers on this box, and the timeout default is the real difference:

8. How to fix it

For the plain cross-language version of this error outside a database context, see ERR_CONNECTION_REFUSED / ECONNREFUSED. If your connection hangs with no refusal at all rather than an eventual error, see ETIMEDOUT.

Frequently asked questions

What is the exact error for a MongoDB connection refused in Node?

With the official mongodb driver (measured, v7.7.0) it's a MongoServerSelectionError whose message is "connect ECONNREFUSED 127.0.0.1:<port>" and whose .cause holds the raw ECONNREFUSED. With mongoose (measured, v9.10.3) the class name is MongooseServerSelectionError instead, same message.

Why does my app take 30 seconds to fail when MongoDB is down?

That's the driver's default serverSelectionTimeoutMS. We measured that the real TCP refusal happens in milliseconds, but the driver retries server selection internally (about every 500ms, per our heartbeat measurements) and only throws after the full 30000ms default elapses. Set serverSelectionTimeoutMS lower if you need a faster failure; we measured 2000ms working with the same error.

Is this different from how Postgres or Redis fail?

Yes, measured on this box: pg and ioredis both surface ECONNREFUSED within milliseconds, with no mandatory wait. MongoDB's driver buries the same millisecond-fast refusal inside a 30-second default retry window before your code sees anything.

Does MongoDB automatically reconnect if the server comes back?

Yes, measured: we stopped a live container mid-session and started a fresh one 5 seconds later with no reconnect code. The driver kept retrying heartbeats on its own and picked the new server up as soon as it was listening. Queries issued during the outage stayed pending and resolved successfully once the server returned, rather than failing individually.

I'm on MongoDB Atlas and get a timeout, not ECONNREFUSED. Why?

Per MongoDB's documentation (not measured on this box, since Atlas needs a cloud account), an IP not on the project's allowlist is typically dropped rather than actively refused, so you get a timeout-style failure inside MongoServerSelectionError rather than an immediate ECONNREFUSED. It can still take up to the full serverSelectionTimeoutMS to surface. Check the Atlas Network Access allowlist before assuming it's a generic network problem.

Written with AI assistance. Measured on 2026-10-02 with Node.js 24.21.0, the official `mongodb` npm driver v7.7.0, and `mongoose` v9.10.3, against a real mongo:7 Docker image (mongod 7.0.43) on 127.0.0.1:27018, and against a definitely-closed local port 27019. This box has no mongod or mongosh binaries, only Docker. MongoDB Atlas's IP-allowlist behaviour is labelled as such and was not measured. PlainHeaders is our extension; curl, Node.js, Docker and the mongodb/mongoose packages are free.