A practical guide for a 3-node replica set

  • 1 preferred master, 2 replicas ( do not mention slave, use replica ..)

MongoDB replica sets do not have a permanent “master.” MongoDB uses the term PRIMARY, and the PRIMARY can move after elections (restart, network hiccup, maintenance).

If your PRIMARY has shifted to a replica node and you want it back on your preferred “master” server, the right approach is to set election priority and trigger a controlled election.

In this article, server names are masked like this, oh this using Debian 12, I do not have master replica yet on Debian 13 Trixie

  • mongo1.internal:27017 = preferred “master” node (you want this to be PRIMARY, because maybe the resource and performance is better)
  • mongo2.internal:27017 = replica
  • mongo3.internal:27017 = replica (currently PRIMARY)

This is safe and standard for MongoDB 8 running on Debian 12.

Why MongoDB PRIMARY Moves ?

MongoDB automatically elects a PRIMARY based on health, replication freshness, votes, and priority. If your preferred node restarts later than the others, or briefly becomes unavailable, another member can win the election and stay PRIMARY.

Your goal is not to “force forever,” but to make mongo1 the preferred winner whenever it’s healthy and caught up.

Step 0, Connect with mongosh

Connect to the node that is currently PRIMARY (or any node, but you’ll apply changes on the current PRIMARY).

Example,

mongosh "mongodb://mongo3.internal:27017/?replicaSet=rs0"

If you use authentication/TLS, keep your existing connection flags as-is.

Step 1, Check Current Replica Set State

Run these two checks, they tell you exactly who is PRIMARY and whether all members are healthy and caught up.

Command 1,

rs.conf().members

Command 2,

rs.status().members.map(m => ({name:m.name, state:m.stateStr, health:m.health, optimeDate:m.optimeDate}))

Example output (masked),

[
  {
    name: 'mongo1.internal:27017',
    state: 'SECONDARY',
    health: 1,
    optimeDate: ISODate("2026-01-09T16:50:45.000Z")
  },
  {
    name: 'mongo2.internal:27017',
    state: 'SECONDARY',
    health: 1,
    optimeDate: ISODate("2026-01-09T16:50:45.000Z")
  },
  {
    name: 'mongo3.internal:27017',
    state: 'PRIMARY',
    health: 1,
    optimeDate: ISODate("2026-01-09T16:50:45.000Z")
  }
]

If optimeDate is the same (or very close) across nodes, it’s a good sign: your preferred node is caught up and eligible to win an election.

Step 2, Make mongo1 the Preferred PRIMARY (Priority)

Run this on the current PRIMARY (in the example, that’s mongo3).

cfg = rs.conf()

cfg.members.forEach(m => {
  if (m.host === "mongo1.internal:27017") {
    m.priority = 2
  } else {
    m.priority = 1
  }
})

rs.reconfig(cfg)

Verify priorities applied,

rs.conf().members.map(m => ({ host: m.host, priority: m.priority, votes: m.votes }))

Example output

[
  { host: 'mongo1.internal:27017', priority: 2, votes: 1 },
  { host: 'mongo2.internal:27017', priority: 1, votes: 1 },
  { host: 'mongo3.internal:27017', priority: 1, votes: 1 }
]

Step 3, Trigger a Controlled Election (Step Down Current PRIMARY)

Still on the current PRIMARY,

rs.stepDown(60)

This forces the current PRIMARY to step down for 60 seconds. With higher priority and equal freshness, mongo1 should be elected PRIMARY.

Re-check status:

rs.status().members.map(m => ({name:m.name, state:m.stateStr, health:m.health, optimeDate:m.optimeDate}))

Expected output:

[
  {
    name: 'mongo1.internal:27017',
    state: 'PRIMARY',
    health: 1,
    optimeDate: ISODate("2026-01-09T16:52:10.000Z")
  },
  {
    name: 'mongo2.internal:27017',
    state: 'SECONDARY',
    health: 1,
    optimeDate: ISODate("2026-01-09T16:52:10.000Z")
  },
  {
    name: 'mongo3.internal:27017',
    state: 'SECONDARY',
    health: 1,
    optimeDate: ISODate("2026-01-09T16:52:10.000Z")
  }
]

If mongo1 Still Doesn’t Become PRIMARY

These are the common blockers in real life.

mongo1 is configured as “never primary”
Check rs.conf().members and confirm mongo1 does not have

  • priority: 0
  • hidden: true
  • votes: 0

No majority
A 3-member replica set needs 2 reachable voting members to elect a PRIMARY. If you’re missing a node, elections may not behave as expected.

Replication lag
If mongo1 is behind, MongoDB may not elect it even with higher priority. Compare optimeDate values again and confirm mongo1 is close to the PRIMARY.

DNS/hostname mismatch
Your m.host match must be exact. Copy it directly from rs.conf().members to avoid typos.

Production Notes

A PRIMARY switch causes a brief write interruption. Well-configured drivers will reconnect automatically if your connection string includes the replica set name and multiple hosts. If you have strict write timeouts, do this during low traffic.

Cheat Sheet (Minimal Commands)

Check

rs.conf().members
rs.status().members.map(m => ({name:m.name, state:m.stateStr, health:m.health, optimeDate:m.optimeDate}))

Prefer mongo1 and move PRIMARY

cfg = rs.conf()
cfg.members.forEach(m => { m.priority = (m.host === "mongo1.internal:27017") ? 2 : 1 })
rs.reconfig(cfg)
rs.stepDown(60)

That’s all folks

Leave A Comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.