Symptom
MongoServerSelectionError: Server selection timed out ... ReplicaSetNoPrimary
NotWritablePrimary
rs.status().members.map(m => ({ name: m.name, state: m.stateStr, health: m.health, lastHeartbeatMessage: m.lastHeartbeatMessage }))
All reachable members are SECONDARY (or RECOVERING), none PRIMARY.
Why there's no primary
A primary needs votes from a majority of voting members. In a 3-member set, 2 must see each other.
| Cause | Typical sign |
|---|---|
| Majority of members down | health: 0 for 2 of 3 members |
| Network partition | Members healthy but can't reach each other; heartbeat timeouts |
| Remaining members not electable | Priority 0, hidden, arbiter only, or too far behind (catchUpTimeoutMillis) |
| Clock / DNS / TLS problems after a change | Heartbeat errors mentioning hostnames or certificates |
Members in RECOVERING / STARTUP2 | Initial sync or too stale to recover |
Step 1: count voting members you can reach
rs.conf().members.map(m => ({ host: m.host, votes: m.votes, priority: m.priority, arbiterOnly: m.arbiterOnly }))
From each node, test connectivity to the others on port 27017 (nc -zv host 27017) and check that the hostnames in rs.conf() resolve correctly from each node.
Step 2: fix the normal causes first
- Start stopped
mongodprocesses; check their logs (/var/log/mongodb/mongod.log) for startup errors such as disk full or a lock file. - Fix network/security group/firewall rules between members.
- Fix DNS (members are addressed by the hostnames in the config).
- Renew expired TLS certificates used for member authentication.
Once a majority can talk, an election happens within seconds (electionTimeoutMillis, default 10 s).
Step 3: majority is gone for good
Example: 3-member set in two data centres, DC1 (2 members) burned down, DC2 has 1 member. No election is possible. To restore writes, force a new configuration on the surviving member:
cfg = rs.conf()
cfg.members = cfg.members.filter(m => m.host === "mongo3.dc2.example.com:27017")
rs.reconfig(cfg, { force: true })
The surviving member becomes primary of a one-member set. Then add new members as soon as possible:
rs.add({ host: "mongo4.dc2.example.com:27017" })
rs.add({ host: "mongo5.dc2.example.com:27017" })
Caution
A forced reconfig on a member that's behind loses writes it never received. If the "lost" members come back with their old config, they must not rejoin as-is: they may have writes the new primary doesn't (rolled back into
rollback/files) and can cause a split-brain if the network was only partitioned. Make sure the old members are really down, then wipe and resync them.
Step 4: after a primary exists
rs.status(): one PRIMARY, secondaries catching up (optimeDate).- Look for rollback files in each member's
dbpath/rollback/and review them with the application team. - Check write concern: apps using
w: "majority"will wait or fail until enough members are back.
Design lessons
| Problem | Fix |
|---|---|
| 2 members + arbiter in one site | Spread voting members across 3 failure domains (e.g. 3 zones or 2 sites + tie-breaker) |
| Only one DC survives in practice | Plan for a forced reconfig with a written runbook, or use 3 sites |
Arbiter with w: majority | Avoid arbiters when using majority write concern; they count for elections but hold no data |
Prevent it next time
- Odd number of voting members across independent failure domains.
- Monitoring on member state and replication lag, not just process health.
- DR runbook for forced reconfig, rehearsed.