VPS Snaps

How to back up a MongoDB replica set

Back up a MongoDB replica set from a member that serves no traffic: add a hidden, priority 0 member, connect to it directly, and run mongodump --oplog so the dump reflects one moment. Restore with mongorestore --oplogReplay into a new one-member replica set, then add members and let them initial sync. The replicas themselves are not a backup: a dropped collection reaches every member as fast as any other write.

9 min readUpdated Checked against official documentation

Connect to the replica set, not one host

A replica set connection string lists several members and names the set, so the client finds the current primary itself, even after a failover. Keep it in a config file, as in the mongodump guide, which also creates the backup user:

/root/.mongodump-rs.yaml
uri: mongodb://backup:[email protected]:27017,db2.example.net:27017,db3.example.net:27017/?replicaSet=rs0&authSource=admin&readPreference=secondary
  • replicaSet=rs0 is the set's name, the _id in rs.conf().
  • authSource=admin: the user lives in admin.
  • readPreference=secondary reads from a secondary instead of the primary, mongodump's default. A --readPreference flag overrides it.

The built-in backup role covers replica set dumps, --oplog included: it can read every non-system collection, those in local too.

Keep the dump off the primary

mongodump reads every document, and MongoDB warns that it pushes the working set out of memory when the data is larger than RAM. readPreference=secondary spares the primary, with two catches. Server selection happens once per operation, at random among secondaries within the latency window, so with two or more secondaries one dump can read from several members. And a secondary that serves application reads still slows down. MongoDB describes an --oplog dump as a full dump of a replica set member, so dump one member that serves no traffic: a hidden member.

Add a hidden member for backups

A hidden member keeps a full copy of the data, cannot become primary, and is left out of db.hello(), so drivers never send it application reads. MongoDB names backups as its most common use. Add one from a mongosh session on the primary:

mongosh
rs.add({ host: "backup.example.net:27017", priority: 0, hidden: true, votes: 0 })
  • priority: 0: it can never become primary. Hidden members must have priority 0.
  • hidden: true: clients can only reach it with a direct connection.
  • votes: 0: no part in elections or in acknowledging w: "majority" writes, so locking or stopping it can't cost the set its majority. Non-voting members must have priority 0.

A member that doesn't vote leaves the others to form a majority, so keep an odd number of voting members, three at least, as MongoDB recommends. To convert an existing secondary, edit it by its position in the members array, not its _id. rs.reconfig() can make the primary step down and close client connections, typically for 10 to 20 seconds:

mongosh
cfg = rs.conf()
cfg.members[3].priority = 0
cfg.members[3].hidden = true
cfg.members[3].votes = 0
rs.reconfig(cfg)

Point a second config file at that member alone; directConnection=true stops the client looking for the others:

/root/.mongodump-hidden.yaml
uri: mongodb://backup:[email protected]:27017/?directConnection=true&authSource=admin
Terminal
mongodump --config=/root/.mongodump-hidden.yaml --oplog --gzip --archive=/var/backups/mongodb/rs0-$(date +%F).archive.gz

What --oplog gives you, and its limits

With --oplog, mongodump also saves the oplog entries written while it runs, and mongorestore --oplogReplay applies them after the data, so the restore matches the moment the dump finished. On a replica set, the limits matter:

  • Full dumps only. MongoDB lists --db, --collection, --query and --dumpDbUsersAndRoles as incompatible; with --db, mongodump stops with --oplog mode only supported on full dumps.
  • Some operations abort it: renaming a collection, $out, mapReduce, and changes to users, roles or cluster-wide settings during the dump.
  • The oplog must outlast the dump. If the entry noted at the start is overwritten, mongodump fails with oplog overflow: mongodump was unable to capture all new oplog entries during execution.
  • One moment only. MongoDB's tools tutorial says --oplogReplay does not restore to an arbitrary point in time.

Size the oplog window

The oplog window is the time between the oldest and newest entries in the oplog. A member that falls further behind can't catch up and needs a full resync, which deletes its data and copies everything again. The window must cover your longest dump, the longest time a backup member is locked or stopped, and any configured delay. Check it on each member:

mongosh
rs.printReplicationInfo()

log length start to end is the window; configured oplog size is the cap. On a new set whose oplog hasn't filled yet, the window only shows how old the set is.

Example: with an 18-hour window, a 40-minute dump is safe and a backup member can be down most of a working day. If batch jobs shrink the window to 30 minutes, the same dump fails with oplog overflow. To raise the cap to 16 GB, run this on every member; it doesn't replicate:

mongosh
db.adminCommand({ replSetResizeOplog: 1, size: Double(16384) })

size is in megabytes, 990 at least. minRetentionHours instead keeps entries for a number of hours, growing past the cap if needed, so watch disk space.

db.getReplicationInfo().timeDiffHours returns the window in hours, ready for monitoring. Alert well before it nears your longest dump.

Keep a delayed member for mistakes

A delayed member applies writes a fixed time late. MongoDB calls it a rolling backup against operator errors such as dropped databases and collections. It must be hidden with priority 0, and MongoDB advises making it non-voting. A one-hour delay on the member at index 4:

mongosh
cfg = rs.conf()
cfg.members[4].priority = 0
cfg.members[4].hidden = true
cfg.members[4].votes = 0
cfg.members[4].secondaryDelaySecs = 3600
rs.reconfig(cfg)

secondaryDelaySecs replaced slaveDelay in MongoDB 5.0; older guides use the old name. The delay must fit inside the oplog window, or the member can't replicate.

Example: someone drops shop.orders at 14:05. The delayed member applies the drop at 15:05, so you have an hour to dump the collection from it, with a config file like the hidden member's:

Terminal
mongodump --config=/root/.mongodump-delayed.yaml --db=shop --collection=orders --gzip --archive=orders-before-drop.archive.gz

Load it back with mongorestore --nsInclude="shop.orders". The delay only helps if someone notices in time, so it adds to nightly dumps rather than replacing them.

Snapshot a member's disk

Disk snapshots (LVM or cloud volume snapshots) restore large data sets faster than dumps. A snapshot of a running member is valid when the journal is on the same volume as the data files. Journaling is always on since MongoDB 6.1, in a journal directory under dbPath, so a dbPath on one volume qualifies.

If the journal is on a separate volume, flush and lock the member first, on a secondary that receives no reads, which is the hidden member:

mongosh
db.fsyncLock()

Take the snapshot, then run db.fsyncUnlock(). Locks stack: writes resume only when lockCount is back to 0. The user needs the fsync and unlock actions, which the hostManager role has. The member catches up afterwards if the lock lasted less than the oplog window.

A snapshot usually lives on the same storage as the disk it copies. MongoDB's snapshot tutorial says to copy the data to other systems; otherwise losing the volume or the account loses the backup too.

Restore into a new replica set

MongoDB's procedure starts the new set with one member, loads the data there, and adds the others afterwards. Its docs warn that you cannot restore a single data set to three new mongod instances and then create a replica set; the secondaries would be forced into an initial sync anyway.

  1. Start one empty mongod with --replSet and the new set's name, on the source's MongoDB version: archives and oplog replays restored across versions can fail or corrupt metadata.
  2. Initiate a one-member set on it (below).
  3. Restore the archive with --oplogReplay, through a config file whose URI points at the new member.
  4. Add each other member with rs.add(), from an empty data directory; each runs an initial sync. For a large database, MongoDB suggests copying the data files to each host instead.
mongosh
rs.initiate({ _id: "rs1", members: [ { _id: 0, host: "db1.new.example.net:27017" } ] })
Terminal
mongorestore --config=/root/.mongorestore-new.yaml --oplogReplay --gzip --archive=rs0-2026-10-04.archive.gz

With access control on, --oplogReplay needs more than the restore role. MongoDB says to create a role with anyAction on anyResource and grant it only to users who must replay the oplog. Revoke it with db.revokeRolesFromUser() when the restore is done:

mongosh
use admin
db.createRole({
  role: "oplogReplayAny",
  privileges: [ { resource: { anyResource: true }, actions: [ "anyAction" ] } ],
  roles: []
})
db.grantRolesToUser("restorer", [ "restore", "oplogReplayAny" ])

From a disk snapshot: start a standalone mongod on the restored files with the source's startup options, drop the local database (the old set's configuration and oplog), shut it down, restart it with --replSet, run rs.initiate(), and add members.

mongosh
use local
db.dropDatabase()

Sharded clusters

Dumping each shard on its own does not give a consistent cluster. MongoDB's sharded mongodump procedure needs MongoDB 7.1 (or 7.0.2, 6.0.11, 5.0.22), a stopped balancer and a cluster-wide fsyncLock() through mongos for the whole dump, without --oplog. For backups while writes continue, MongoDB points to its coordinated backup services.

What replication does not protect against

  • Mistakes and bad code. A drop, a deleteMany({}) or a broken migration replicates like any other write.
  • A stolen admin account or ransomware. Whoever can write to the primary can wipe every member.
  • Losing the hosting account or region when every member lives there.

Replication keeps the database running when a server dies. The rest needs copies outside the set, kept for days or weeks with another provider, as in the 3-2-1 rule.

Test a restore

Restore the newest archive into a throwaway one-member replica set on the same version and compare counts. The official Docker image passes its arguments to mongod:

Terminal
docker run -d --name rs-restore-test -p 127.0.0.1:27018:27017 mongo:8.0 --replSet restoretest
Terminal
docker exec rs-restore-test mongosh --quiet --eval 'rs.initiate({ _id: "restoretest", members: [ { _id: 0, host: "127.0.0.1:27017" } ] })'
Terminal
time mongorestore --uri="mongodb://127.0.0.1:27018/?directConnection=true" --oplogReplay --gzip --archive=rs0-2026-10-04.archive.gz

time reports how long the restore took: your real recovery time for this method (see RPO and RTO). Count documents in a few collections on both sides, then remove the container with docker rm -f rs-restore-test. Repeat monthly and after upgrades, as in testing a backup restore.

Common errors

ErrorFix
--oplog mode only supported on full dumpsRemove --db and dump everything, or drop --oplog.
oplog overflow: mongodump was unable to capture all new oplog entries during executionThe oplog rolled over during the dump. Grow it with replSetResizeOplog, or dump at a quieter time.
can't use --oplog option when dumping from a mongosThat is a sharded cluster. Follow MongoDB's sharded procedure.
no oplog file to replay; make sure you run mongodump with --oplogThe archive was made without --oplog. Restore without --oplogReplay.
cannot use --oplogReplay with includes specified--nsInclude and --oplogReplay don't mix. Restore the whole archive into a scratch set, then copy out what you need.

Frequently asked questions

Is a MongoDB replica set a backup?
No. Deletes and bad writes reach every member. Keep dumps or snapshots outside the set; a delayed member adds a short undo window.
Should I run mongodump on the primary or a secondary?
A secondary: ideally a hidden, priority 0, non-voting member reached with directConnection=true, so the dump stays on one member and away from application traffic.
Does mongodump --oplog give point-in-time recovery?
Only to the moment the dump finished. It makes the dump consistent, but it cannot restore to an arbitrary time.
How large should the oplog be for backups?
Its window, shown by rs.printReplicationInfo(), should comfortably exceed your longest dump, any time a backup member is locked or down, and any member's delay.

How this was checked

Commands, limits and prices were checked against these official pages, on October 4, 2026: