The Error
MongoServerError: Topology was destroyed
You fire a query, an insert, or an aggregation โ and instead of a result, you get this. The operation never ran. MongoDB's internal connection topology was torn down before (or while) your code tried to use it.
Root Cause
MongoDB drivers maintain a topology: an internal object tracking live connections, server health, and the connection pool. "Topology was destroyed" means the driver shut that object down while your code was still pointing at it.
The usual suspects:
- You called
client.close()(ormongoose.disconnect()) and then ran a query afterward โ classic async timing bug. - The process caught
SIGINT/SIGTERMand the shutdown handler closed the connection while in-flight queries were still pending. serverSelectionTimeoutMSexpired โ the driver couldn't find a usable server within the timeout window (default: 30 seconds) and destroyed the topology instead of retrying.- A
MongoClientwas created inside a function or request handler and closed at the end โ the next call sometimes still references the old, closed instance. - An unhandled promise rejection crashed the app mid-operation, tearing down active connections abruptly.
Fix 1 โ Don't Close the Client Too Early (Most Common)
Scripts and Lambda-style functions are the biggest offenders here. A missing await lets client.close() run before the query even starts:
// BAD โ client closes before findOne resolves
const client = new MongoClient(uri);
await client.connect();
client.db('mydb').collection('users').findOne({ id: 1 }); // no await!
await client.close(); // topology gone before findOne completes
// GOOD โ await every operation before closing
const client = new MongoClient(uri);
try {
await client.connect();
const user = await client.db('mydb').collection('users').findOne({ id: 1 });
console.log(user);
} finally {
await client.close();
}
Every database call needs await. If you're using .then() chains, client.close() must be chained at the very end โ not alongside other operations.
Fix 2 โ Fix Shutdown Race Conditions
Long-running servers hit this when the process shuts down mid-request. A naive SIGTERM handler closes MongoDB immediately, leaving active queries stranded:
// BAD โ closes DB while HTTP requests may still be running
process.on('SIGTERM', async () => {
await mongoose.disconnect();
process.exit(0);
});
// GOOD โ drain HTTP first, then close DB
process.on('SIGTERM', async () => {
server.close(async () => { // stop accepting new HTTP requests
await mongoose.disconnect(); // only now close MongoDB
process.exit(0);
});
});
Stop the HTTP server โ wait for active connections to drain โ close MongoDB. Skipping any step causes this error under real traffic.
Fix 3 โ Increase serverSelectionTimeoutMS
Under high load or with a slow network, the driver can time out trying to find a usable server. Once it gives up, it destroys the topology. Bump the timeout:
// Node.js โ mongodb driver
const client = new MongoClient(uri, {
serverSelectionTimeoutMS: 10000, // bump from the default 30000 if failing fast
connectTimeoutMS: 10000,
socketTimeoutMS: 45000,
});
# mongoose
mongoose.connect(uri, {
serverSelectionTimeoutMS: 10000,
connectTimeoutMS: 10000,
socketTimeoutMS: 45000,
});
Before tweaking timeouts, verify the MongoDB server is actually reachable:
mongosh "mongodb://user:pass@host:27017/dbname" --eval "db.adminCommand({ ping: 1 })"
A successful ping rules out network issues entirely.
Fix 4 โ Reuse One MongoClient (Don't Create Per-Request)
Creating a fresh MongoClient per request โ then closing it โ is a surprisingly common anti-pattern. Under any concurrency, overlapping requests end up sharing a topology that one of them just closed:
// BAD โ new client per request, race condition waiting to happen
app.get('/users', async (req, res) => {
const client = new MongoClient(uri);
await client.connect();
const users = await client.db('mydb').collection('users').find().toArray();
await client.close(); // next overlapping request may still reference this topology
res.json(users);
});
// GOOD โ one client for the whole process
const client = new MongoClient(uri);
await client.connect();
app.get('/users', async (req, res) => {
const users = await client.db('mydb').collection('users').find().toArray();
res.json(users);
});
MongoDB's built-in connection pool (100 connections by default) handles all the concurrency for you. One MongoClient per process is the right model.
Fix 5 โ Add Retry Logic for Critical Operations
Network blips happen. Wrapping critical queries in retry logic keeps transient topology errors from surfacing to users:
async function withRetry(fn, maxRetries = 3) {
for (let attempt = 1; attempt setTimeout(r, 1000 * attempt)); // 1s, 2s, 3s backoff
continue;
}
throw err;
}
}
}
// Usage
const result = await withRetry(() =>
db.collection('orders').findOne({ _id: orderId })
);
The exponential backoff (1 s, 2 s, 3 s) gives the driver time to recover without hammering a struggling server.
Fix 6 โ pymongo (Python)
Python surfaces this differently โ usually as pymongo.errors.InvalidOperation: Cannot use MongoClient after close โ but the root cause is identical. The fix is the context manager:
from pymongo import MongoClient
# BAD โ client closed before find_one runs
client = MongoClient(uri)
db = client['mydb']
client.close()
db.users.find_one() # topology already destroyed
# GOOD โ context manager closes the client only after the block exits
with MongoClient(uri) as client:
db = client['mydb']
result = db.users.find_one({'id': 1})
print(result)
Verify the Fix
Don't just restart and hope. Confirm the error is actually gone:
- Trigger the previously failing code path and check logs โ no more
Topology was destroyedentries. - Check connection pool health in MongoDB Atlas metrics or directly:
db.serverStatus().connections
// { current: 5, available: 195, totalCreated: 12 }
- Run a load test with
autocannonork6(50โ200 concurrent users works well for most APIs) and confirm zero topology errors across all requests.
Prevention
- One client per process โ initialize
MongoClientonce at startup, share it everywhere in the app. - Graceful shutdown order โ drain HTTP connections first, then call
mongoose.disconnect()orclient.close(). Never the other way around. - Await everything โ no MongoDB operation should fire without
awaitor a proper.then()chain. A linter rule (no-floating-promisesin TypeScript) catches this automatically. - Tune timeouts to match reality โ if your MongoDB Atlas cluster is in
us-east-1and your app runs inap-southeast-1, a 5 sserverSelectionTimeoutMSwill hurt you. Measure actual round-trip latency first. - Watch the connection pool โ alert when
connections.availabledrops below 20. Pool exhaustion is often what triggers topology destruction under load.

