Home / Databases / MongoDB

DatabasesRunbookIntermediate

MongoDB backup and restore runbook - mongodump with oplog, filesystem snapshots and point-in-time recovery

Choose between mongodump (logical, small data), filesystem/volume snapshots (large data) and managed backups. Take consistent backups from a replica set, restore them, replay the oplog for point-in-time recovery and test regularly.

Deepak Bhardwaj4 min read

Applies toMongoDB Community and Enterprise 6.0-8.0 replica sets; MongoDB Database Tools 100.x; Linux with LVM or cloud disk snapshots

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)
  • --oplog captures 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).
  • --gzip compresses each file.
  • For a single database, add --db sales and drop --oplog. The dump is then not point-in-time consistent across collections.
  • mongodump doesn'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

  1. Stop mongod on the target (or build a new member).
  2. Replace the dbPath contents with the snapshot data.
  3. Start mongod. To use it as a standalone copy for inspection, start it without the replication.replSetName setting.
  4. 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.

  1. Restore the 02:00 backup to a separate instance.
  2. 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
  1. Find the timestamp of the bad operation:
use local
db.oplog.rs.find({ op: "c", "o.drop": "orders" }).sort({ $natural: -1 }).limit(1)
  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.

  1. 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