Fix MongoServerError: not primary When Writing to a MongoDB Replica Set

intermediate๐Ÿƒ MongoDB2026-03-18| MongoDB 4.x / 5.x / 6.x, Replica Set deployment, any OS (Linux/macOS/Windows), drivers: Node.js (mongoose/mongodb), Python (pymongo), Java

Error Message

MongoServerError: not primary
#mongodb#replicaset#primary#write-concern

The Error

You run an insert, update, or delete against your MongoDB cluster and hit this:

MongoServerError: not primary

Writes are suddenly rejected. Nine times out of ten, your app is talking to a secondary node instead of the primary. The other case: the primary just stepped down and election hasn't finished yet.

Why This Happens

In a replica set, only the primary accepts writes. Secondaries are read-only โ€” full stop. The not primary error surfaces in a few specific situations:

  • Your connection string points directly to a secondary's IP/port instead of using the replica set URI.
  • A failover just happened โ€” the old primary stepped down, and no new one has been elected yet.
  • Read preference is set to secondary or secondaryPreferred, but write operations go through the same connection.
  • A hidden or delayed secondary was temporarily promoted during maintenance.
  • DNS or a load balancer is routing traffic to the wrong node.

Step 1 โ€” Check the Replica Set Status

Connect to any node and run:

rs.status()

Scan the members array for the stateStr field on each node:

{
  "members" : [
    { "name" : "mongo1:27017", "stateStr" : "PRIMARY" },
    { "name" : "mongo2:27017", "stateStr" : "SECONDARY" },
    { "name" : "mongo3:27017", "stateStr" : "SECONDARY" }
  ]
}

All nodes showing SECONDARY? Or one stuck in RECOVERING? The replica set has no primary right now. That's normal during an election โ€” wait 10โ€“30 seconds and check again.

To confirm which node you're actually connected to:

db.isMaster()
// or the newer equivalent:
db.hello()

If ismaster (or isWritablePrimary) comes back false, you're on a secondary. That's your problem.

Fix 1 โ€” Use a Replica Set Connection String

The most common culprit. Connecting to a single node gives the driver zero awareness of the replica set topology:

# Wrong โ€” single node, no replica set awareness
mongodb://mongo1:27017/mydb

Switch to the full URI listing all members with the replicaSet parameter:

# Correct โ€” driver discovers the primary automatically
mongodb://mongo1:27017,mongo2:27017,mongo3:27017/mydb?replicaSet=rs0

Now the driver handles failover on its own. Primary changes? It re-discovers the new one. You don't touch anything.

On Atlas, use the SRV format โ€” replica set info is already baked in:

mongodb+srv://user:pass@cluster0.xxxxx.mongodb.net/mydb

Fix 2 โ€” Node.js / Mongoose Connection Update

// Before (broken)
const uri = 'mongodb://192.168.1.10:27017/mydb';

// After (replica set aware)
const uri = 'mongodb://192.168.1.10:27017,192.168.1.11:27017,192.168.1.12:27017/mydb?replicaSet=rs0';

await mongoose.connect(uri, {
  serverSelectionTimeoutMS: 5000,
  heartbeatFrequencyMS: 10000,
});

Fix 3 โ€” Python / PyMongo Connection Update

from pymongo import MongoClient

# Before (broken)
client = MongoClient('mongodb://192.168.1.10:27017/')

# After (replica set aware)
client = MongoClient(
    'mongodb://192.168.1.10:27017,192.168.1.11:27017,192.168.1.12:27017/',
    replicaSet='rs0',
    serverSelectionTimeoutMS=5000
)

# Verify you're on the primary
print(client.primary)  # Should print ('192.168.1.10', 27017)

Fix 4 โ€” During Failover (Temporary State)

Sometimes the cluster just lost its primary โ€” crash, network partition, planned maintenance. Election takes time. Watch it live:

# From mongosh, run this in a loop
while true; do
  mongosh --quiet --eval "rs.status().members.forEach(m => print(m.name, m.stateStr))"
  sleep 3
done

Under normal conditions, election wraps up in under 10 seconds. If it's dragging on, check these three things:

  • Do you have at least 3 nodes? You need a majority vote to elect a primary.
  • Is network connectivity between all nodes healthy?
  • Any nodes stuck in RECOVERING state?

Need to manually kick off a new election? Connect to the current primary and run:

rs.stepDown()  // Steps down current primary, triggers new election

Fix 5 โ€” Check writeConcern Settings

Easy one to miss: a readPreference of secondary on a write operation makes no sense โ€” and can cause this error. Double-check your write calls:

// Wrong โ€” secondary read preference on a write operation
db.collection('orders').insertOne(
  { item: 'widget' },
  { readPreference: 'secondary' }  // This doesn't apply to writes
);

// Correct โ€” write concern separate from read preference
db.collection('orders').insertOne(
  { item: 'widget' },
  { writeConcern: { w: 'majority', wtimeout: 5000 } }
);

Verify the Fix

After updating your connection string, run a quick smoke test from mongosh:

use mydb
db.test.insertOne({ ping: new Date() })
// Should return: { acknowledged: true, insertedId: ... }

The MongoServerError: not primary errors should stop appearing. To confirm the driver landed on the primary:

# Node.js โ€” log the topology
mongoose.connection.on('connected', () => {
  console.log('Connected to:', mongoose.connection.host);
});

Prevention

  • Always use replica set connection strings in production. Hardcoding a single node IP is a ticking clock โ€” failover will happen eventually, usually at 3 AM.
  • Set aggressive timeouts. Configure serverSelectionTimeoutMS and connectTimeoutMS so your app fails fast and retries instead of hanging during elections.
  • Enable retryable writes. Add retryWrites=true to your connection string (the default in most modern drivers). Transient primary changes become invisible to your application.
  • Monitor replica set health proactively. Alert on members entering RECOVERING or UNKNOWN state โ€” don't wait for users to report write errors.
  • Split read and write clients. Reading from secondaries for load distribution? Use a dedicated client with readPreference: 'secondaryPreferred'. Keep your write client targeting primary only.
# Full production-ready connection string
mongodb://mongo1:27017,mongo2:27017,mongo3:27017/mydb
  ?replicaSet=rs0
  &retryWrites=true
  &w=majority
  &readPreference=primaryPreferred
  &serverSelectionTimeoutMS=5000

Related Error Notes