Two MongoDB errors fill support forums:
MongoServerSelectionError: connection timed out
Server selection timed out after 30000 ms
and, under load:
MongoWaitQueueTimeoutError: Timed out while checking out a connection from connection pool
The first usually means the driver can't reach a suitable server at all. The second means it can, but your app has run out of pooled connections, or the Atlas cluster has hit its connection limit. Here's how to tell them apart and fix each one.
Server selection errors: can't reach the cluster
The driver keeps trying to find a server that matches your read preference, for up to serverSelectionTimeoutMS (30 seconds by default). If none responds in time, you get MongoServerSelectionError. Check these in order:
1. Network access (Atlas). The app's IP isn't on the project's IP Access List. That's the most common cause by far, especially with serverless platforms or CI runners whose IPs change. Add the egress IPs, use VPC peering or Private Endpoint, or (temporarily, for testing only) 0.0.0.0/0.
2. The connection string.
mongodb+srv://needs working DNS SRV lookups. Some corporate networks and older resolvers block them, so try the standardmongodb://host1,host2,host3/?replicaSet=…string from Atlas.- The username and password must be URL-encoded (
@,:and/in passwords break the parsing). - For a replica set, the host names the members advertise (in
rs.conf()) must resolve from the client. In Docker or Kubernetes that's a classic trap: the driver discoversmongo-0:27017and can't resolve it. Use proper host names, ordirectConnection=truefor a single-node development setup.
3. TLS. Atlas requires TLS. Old runtimes with outdated CA bundles fail the handshake and show up as selection timeouts.
4. The cluster is unavailable or paused, or in an election. Brief elections are normal, and drivers retry reads and writes once (retryWrites and retryReads are on by default).
Turn on driver logging, or listen for SDAM events (serverHeartbeatFailed), to see the underlying error instead of the generic timeout.
Pool exhaustion: too few connections, or too many clients
Each MongoClient keeps a connection pool (maxPoolSize, default 100 per server). An operation borrows a connection and returns it when done. If all are busy, new operations wait, and eventually time out.
The #1 cause: creating a new MongoClient per request.
// ❌ Every request opens a new pool and a set of TCP+TLS connections
export async function handler(req) {
const client = await MongoClient.connect(process.env.MONGODB_URI);
...
}
// ✅ One client per process, reused
const client = new MongoClient(process.env.MONGODB_URI, { maxPoolSize: 20 });
const clientPromise = client.connect();
export async function handler(req) {
const db = (await clientPromise).db("app");
...
}
In serverless functions (Lambda, Vercel, Netlify), define the client outside the handler so warm invocations reuse it, and keep maxPoolSize small, because hundreds of concurrent instances × 100 connections each will exhaust the cluster. Atlas tiers have hard connection limits (for example a few hundred on small shared or low tiers). Watch the Connections chart in Atlas.
Other causes:
- Slow operations hold connections longer. A missing index that turns a 2 ms query into a 2-second one makes the pool 1,000× busier. Check the Atlas Query Profiler or
db.currentOp()and fix the slow queries first. - Cursors you don't close: iterate them to the end, or call
cursor.close(). - Long transactions hold a connection for their whole duration.
Settings worth knowing
mongodb+srv://.../app?maxPoolSize=20&minPoolSize=0&maxIdleTimeMS=60000
&serverSelectionTimeoutMS=5000&waitQueueTimeoutMS=2000
- A lower
serverSelectionTimeoutMS(for example 5 s) makes outages fail fast instead of hanging requests for 30 seconds. maxIdleTimeMScloses idle connections, which helps with serverless and with firewalls that silently drop idle connections.- Size
maxPoolSizefrom concurrency: about the number of operations a process runs at the same time, not "as big as possible".
Checklist
- Selection timeout: check the IP access list, the connection string (SRV, encoding, advertised hosts) and TLS.
- Pool timeout: reuse one
MongoClientper process, and never one per request. - In serverless, create the client outside the handler, with a small
maxPoolSize. - Fix slow queries and unclosed cursors, which hold connections, and watch Atlas's connection limits.
Get the weekly commit
New database deep dives every week.
