Skip to content

Fix: MongoDB "not primary" Write Error (Replica Set)

FixDevs ·

Quick Answer

How to fix MongoDB 'not primary' errors when writing to a replica set — read preference misconfiguration, connecting to a secondary, replica set elections, and write concern settings.

The Error

A write operation to MongoDB fails with:

MongoServerError: not primary

Or in older MongoDB versions:

MongoError: not master

Or after a failover:

MongoServerError: not primary and secondaryOk=false
MongoNotPrimaryError: Command find requires authentication

Or reads that worked suddenly fail:

MongoServerError: not primary or secondary; cannot currently read from this replSetMember.STATE=RECOVERING

Why This Happens

MongoDB replica sets consist of one primary and one or more secondaries. Only the primary accepts write operations. This error occurs when:

  • Connecting to a secondary — the connection string points to a secondary node directly, or the driver selected a secondary for a write operation.
  • Read preference set to secondary for writes — some drivers allow you to set readPreference, but writes always require the primary regardless of read preference.
  • Replica set election in progress — after a primary failure, the replica set holds an election (typically 10–30 seconds). During this window, no primary exists and all writes fail.
  • Network partition — the primary is isolated from the majority of the replica set and steps down voluntarily to prevent split-brain writes.
  • Member in RECOVERING state — a node that is syncing, or that just rejoined after being down, enters the RECOVERING state and cannot serve reads or writes.
  • Using a direct connection to a specific host — connecting with directConnection=true to a node that is currently a secondary causes immediate write failures.

Fix 1: Use a Replica Set Connection String

Never connect to a single member of a replica set using its hostname directly — always use the full replica set URI so the driver can discover the current primary:

# Wrong — connects directly to one host, fails if it's a secondary
mongodb://mongo1.example.com:27017/mydb

# Correct — lists all members; driver discovers and connects to the primary
mongodb://mongo1.example.com:27017,mongo2.example.com:27017,mongo3.example.com:27017/mydb?replicaSet=rs0

With authentication:

mongodb://username:[email protected]:27017,mongo2.example.com:27017,mongo3.example.com:27017/mydb?replicaSet=rs0&authSource=admin

Atlas connection string (already includes replica set info):

mongodb+srv://username:[email protected]/mydb?retryWrites=true&w=majority

The mongodb+srv:// scheme uses DNS SRV records to discover all replica set members automatically. Always prefer this format for Atlas clusters.

Fix 2: Check and Fix Read Preference

Read preference controls which replica set member the driver sends read operations to. It does not affect where writes go — writes always go to the primary:

// Node.js (Mongoose)
const mongoose = require('mongoose');

// Wrong — setting readPreference to secondary doesn't affect writes,
// but if you're also accidentally routing writes here, it will fail
mongoose.connect(uri, {
  readPreference: 'secondary',  // Reads go to secondary
});

// Correct — primary for writes (default), secondaryPreferred for reads
const client = new MongoClient(uri, {
  readPreference: 'secondaryPreferred',  // Reads prefer secondary, fall back to primary
});

Read preference modes:

ModeDescription
primaryAll reads go to the primary (default)
primaryPreferredReads go to primary, fall back to secondary if unavailable
secondaryAll reads go to a secondary
secondaryPreferredReads prefer secondary, fall back to primary
nearestReads go to the member with lowest network latency

Common Mistake: Setting readPreference: 'secondary' is fine for reads. The error occurs when using directConnection=true to a secondary host or when the connection string targets a secondary directly.

Python (PyMongo):

from pymongo import MongoClient, ReadPreference

# Connect with replica set — driver auto-discovers primary
client = MongoClient(
    "mongodb://mongo1:27017,mongo2:27017,mongo3:27017/",
    replicaSet="rs0",
    readPreference=ReadPreference.SECONDARY_PREFERRED
)

db = client.mydb
# Writes automatically go to primary
db.users.insert_one({"name": "Alice"})  # Goes to primary

# Reads go to secondary (or primary if no secondary available)
users = list(db.users.find())

Fix 3: Handle Replica Set Elections Gracefully

When the primary steps down (due to failure, maintenance, or rs.stepDown()), an election occurs. During the election (typically 10–30 seconds), writes fail with not primary. Configure your driver to retry writes automatically:

// Node.js — enable retryable writes (default in modern drivers)
const client = new MongoClient(uri, {
  retryWrites: true,   // Automatically retry eligible write operations
  retryReads: true,    // Automatically retry eligible read operations
});
# Python — retryable writes (enabled by default in PyMongo 3.9+)
client = MongoClient(
    uri,
    retryWrites=True,
    retryReads=True,
)

Retryable writes cover:

  • insertOne, updateOne, replaceOne, deleteOne
  • Bulk write operations with ordered: true that fail on the first operation
  • findOneAndUpdate, findOneAndReplace, findOneAndDelete

Not covered by retryable writes:

  • insertMany with ordered: false
  • updateMany, deleteMany
  • Any multi-document operation

For operations not covered, add explicit retry logic:

async function writeWithRetry(collection, doc, maxAttempts = 3) {
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      return await collection.insertOne(doc);
    } catch (err) {
      if (
        attempt < maxAttempts &&
        (err.code === 10107 ||  // NotPrimary
         err.message.includes('not primary') ||
         err.message.includes('not master'))
      ) {
        console.warn(`Write attempt ${attempt} failed (not primary) — retrying in ${attempt * 1000}ms`);
        await new Promise(resolve => setTimeout(resolve, attempt * 1000));
        continue;
      }
      throw err;
    }
  }
}

Fix 4: Check Replica Set Status

After a failover or if the error persists, check the replica set health:

// Connect to mongosh and check replica set status
rs.status()
{
  "set" : "rs0",
  "members" : [
    {
      "name" : "mongo1:27017",
      "health" : 1,
      "state" : 1,      // 1 = PRIMARY
      "stateStr" : "PRIMARY",
      ...
    },
    {
      "name" : "mongo2:27017",
      "health" : 1,
      "state" : 2,      // 2 = SECONDARY
      "stateStr" : "SECONDARY",
      ...
    },
    {
      "name" : "mongo3:27017",
      "health" : 0,     // 0 = unhealthy
      "state" : 8,      // 8 = DOWN
      "stateStr" : "DOWN",
      ...
    }
  ]
}

State values to look for:

StateMeaning
1 (PRIMARY)Accepts reads and writes
2 (SECONDARY)Accepts reads (with appropriate read preference)
5 (STARTUP2)Initial sync in progress
6 (UNKNOWN)Cannot communicate with this member
8 (DOWN)Member is unreachable
9 (ROLLBACK)Rolling back operations after rejoining
10 (REMOVED)Member removed from the replica set

If no primary exists:

// Force an election
rs.reconfig(rs.conf(), { force: true })

// Or step down the current primary (from the primary) to trigger election
rs.stepDown(60)  // Steps down for 60 seconds

// Check election status
rs.isMaster()  // Deprecated but still works
rs.hello()     // Modern equivalent

Fix 5: Fix directConnection Issues

The directConnection=true option bypasses replica set topology discovery and connects directly to the specified host. This is useful for local development but causes not primary errors in production:

// Wrong — directConnection skips topology discovery
const client = new MongoClient('mongodb://mongo1:27017/mydb?directConnection=true');

// Correct — use replica set URI for production
const client = new MongoClient(
  'mongodb://mongo1:27017,mongo2:27017,mongo3:27017/mydb?replicaSet=rs0'
);

When directConnection=true is valid:

  • Connecting to a standalone MongoDB instance (not a replica set)
  • Local development with a single MongoDB instance
  • Specifically targeting a secondary for maintenance operations
// Reading from a specific secondary for maintenance (not writes)
const secondaryClient = new MongoClient('mongodb://mongo2:27017/mydb', {
  directConnection: true,
  readPreference: 'secondaryPreferred',
});
// Never use this client for writes

Fix 6: Fix Write Concern Settings

Write concern controls how many replica set members must acknowledge a write before the driver considers it successful. An overly strict write concern on a degraded replica set causes writes to fail or time out:

// w: 'majority' — write must be acknowledged by the majority of members
// This fails if the majority is unreachable (e.g., 2 of 3 members are down)
const result = await collection.insertOne(doc, { writeConcern: { w: 'majority', wtimeout: 5000 } });

// w: 1 — only the primary needs to acknowledge (less safe but more available)
const result = await collection.insertOne(doc, { writeConcern: { w: 1 } });

Connection-level write concern:

const client = new MongoClient(uri, {
  writeConcern: {
    w: 'majority',
    wtimeoutMS: 5000,  // Fail if majority doesn't respond in 5 seconds
    journal: true,     // Wait for journal flush
  },
});

Real-world scenario: A 3-member replica set loses 2 members. With w: 'majority', writes fail because 2 members are needed to acknowledge. Drop to w: 1 temporarily to keep writes flowing — but understand you risk data loss if the remaining primary crashes before replication occurs.

Still Not Working?

Verify the replica set name matches your connection string:

// In mongosh — check the replica set name
rs.conf().set  // e.g., "rs0"

// Connection string must include replicaSet=rs0
mongodb://host1:27017,host2:27017/mydb?replicaSet=rs0

Check network connectivity between application and all replica set members:

# From the application server, test each member
nc -zv mongo1.example.com 27017
nc -zv mongo2.example.com 27017
nc -zv mongo3.example.com 27017

# If any fail, the driver may not be able to discover the primary

Check if the member is in RECOVERING state — a recovering member can’t accept reads or writes. Wait for it to finish syncing (check rs.status() and look at the optime lag).

Force a new initial sync for a stuck member:

# Stop mongod on the problematic member
sudo systemctl stop mongod

# Delete the data directory (this triggers a full resync)
sudo rm -rf /var/lib/mongodb/*

# Restart — mongod will sync from another member
sudo systemctl start mongod

For related MongoDB issues, see Fix: MongoDB Connect ECONNREFUSED and Fix: MongoDB Duplicate Key Error.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles