Pick the method
| Method | Good for | Limits |
|---|---|---|
mongodump / mongorestore | Small databases (tens of GB), single collections, migrations | Slow on large data; restore rebuilds indexes |
| Filesystem / volume snapshot | Large databases, fast restore | Needs journalling on the same volume, or fsyncLock |
| Atlas / Ops Manager / Cloud Manager backup | Managed point-in-time recovery | Product-specific |
| Hidden or delayed secondary | Quick recovery from mistakes | Not a backup on its own (same cluster, same mistakes replicate after the delay) |
Warning
A replica set is not a backup.
db.collection.drop()replicates to every member within seconds.
Option 1: mongodump with oplog
Run against a secondary to keep load off the primary:
mongodump --uri "mongodb://backup:<pwd>@db2.example.com:27017/?authSource=admin&readPreference=secondary" \
--oplog --gzip --out /backup/mongo/$(date +%F_%H%M)
--oplogcaptures writes that happen during the dump, so the backup is consistent to one point in time. It only works for a full-instance dump (no--db).--gzipcompresses each file.- For a single database, add
--db salesand drop--oplog. The dump is then not point-in-time consistent across collections. mongodumpdoesn't work for sharded clusters as a consistent backup. Use a managed backup tool or coordinated snapshots for those.
The backup user needs the backup role. For restores, use a user with the restore role.
use admin
db.createUser({ user: "backup", pwd: passwordPrompt(), roles: ["backup"] })
Restore
mongorestore --uri "mongodb://restore:<pwd>@newdb1.example.com:27017/?authSource=admin" \
--oplogReplay --gzip /backup/mongo/2026-10-26_0200
Restore one collection into a different name, to compare before overwriting:
mongorestore --uri "..." --gzip \
--nsInclude "sales.orders" --nsFrom "sales.orders" --nsTo "sales.orders_restored" \
/backup/mongo/2026-10-26_0200
--drop drops each collection before restoring it. Use it only when you mean to replace the data.
Option 2: filesystem snapshot
With journalling enabled and the data files and journal on the same volume, a single volume snapshot is consistent without locking. Take it from a secondary:
# LVM example
lvcreate --size 50G --snapshot --name mdb-snap-$(date +%F) /dev/vg_data/lv_mongo
For cloud disks, use the provider's disk snapshot (EBS, Azure managed disk, OCI block volume backup).
If the data and journal are on different volumes, or you're not sure, lock writes for the duration of the snapshot:
db.fsyncLock()
// take the snapshot(s)
db.fsyncUnlock()
Caution
Always run
fsyncUnlock(), even if the snapshot fails. A locked member stops applying writes and falls behind. Script the lock and unlock with error handling.
Restore from a snapshot
- Stop
mongodon the target (or build a new member). - Replace the dbPath contents with the snapshot data.
- Start
mongod. To use it as a standalone copy for inspection, start it without thereplication.replSetNamesetting. - To add it back into the replica set, start it with the set name. It catches up from the oplog if the oplog still covers the gap; otherwise it needs an initial sync.
Point-in-time recovery
Example: someone dropped a collection at 10:42. You have a 02:00 backup.
- Restore the 02:00 backup to a separate instance.
- Dump the oplog from the production replica set, from the backup time to just before the mistake:
mongodump --uri "mongodb://backup:<pwd>@db2.example.com/?authSource=admin" \
-d local -c oplog.rs \
--query '{"ts": {"$gte": {"$timestamp": {"t": 1793066400, "i": 1}}}}' \
--out /backup/oplog-pitr
- Find the timestamp of the bad operation:
use local
db.oplog.rs.find({ op: "c", "o.drop": "orders" }).sort({ $natural: -1 }).limit(1)
- Replay up to (but not including) it:
mongorestore --uri "mongodb://restore:<pwd>@recovery1:27017/?authSource=admin" \
--oplogReplay --oplogLimit 1793097720:1 /backup/oplog-pitr/local/oplog.rs.bson
When restoring from a dump directory, mongorestore expects the oplog file as oplog.bson. Move or rename the file as the Database Tools documentation describes for your version.
- Copy the recovered data back to production with
mongodump/mongorestore --nsInclude.
This only works if the oplog still covers the period since the backup. Check the oplog window:
rs.printReplicationInfo()
Size the oplog to cover at least the time between backups, plus a margin.
Test restores
- Restore the latest backup to a test instance every month.
- Check counts and a sample of documents against production.
- Record how long the restore took. That's your real RTO.
db.getSiblingDB("sales").orders.countDocuments({})
Checklist
- Method matches data size and RTO
- Backups taken from a secondary
- mongodump with --oplog for full-instance dumps
- Snapshots consistent (same volume, or fsyncLock with unlock)
- Oplog window longer than the backup interval
- Monthly restore test with timings