MongoDB data protection is only real when you routinely prove you can restore it. This guide gives you practical, copy-paste steps for backups, restores, validation, rollback planning, and disaster recovery. You will finish with a tested local pilot and a clear path to production.
Who this is for:
- Developers, DevOps consultants, and startup teams who need a reliable, low-drama plan
- Anyone who wants a repeatable workflow that can be tested locally first
What you will do:
- Take consistent MongoDB backups (mongodump and filesystem snapshots)
- Restore to a safe target, validate thoroughly, and plan a clean cutover
- Rehearse disaster scenarios and measure RTO/RPO
Workflow Overview
A dependable production workflow is simple and explicit:
- Choose your backup method
- mongodump for logical backups; use --oplog for replica sets
- Filesystem snapshots for high-throughput workloads and sharded clusters
- Schedule and store backups
- Run on a secondary when possible, compress and encrypt at rest, keep multiple generations
- Test restores regularly
- Restore to a staging namespace, never directly into production
- Validate results
- Counts, hashes, sample queries, indexes, users/roles, fCV
- Plan rollback and cutover
- Restore to parallel collections, then swap atomically
- Rehearse disaster recovery
- Time each step, fix bottlenecks, and document the runbook
Backup examples (replica set)
Create least-privilege users for backup and restore.
// In mongosh on the primary
use admin
// Backup user
db.createUser({
user: "backup",
pwd: "REDACTED_STRONG_PW",
roles: [ { role: "backup", db: "admin" } ]
})
// Restore user (use only on non-prod or DR target)
db.createUser({
user: "restore",
pwd: "REDACTED_STRONG_PW",
roles: [ { role: "restore", db: "admin" } ]
})
Environment helpers:
export RS_URI='mongodb://backup: REDACTED_STRONG_PW@rs0/yourdb?replicaSet=rs0&authSource=admin'
export TS=$(date -u +%Y%m%dT%H%M%SZ)
mkdir -p backups
Full database dump from a secondary with oplog for point-in-time safety:
mongodump \
--uri "$RS_URI" \
--readPreference secondaryPreferred \
--db yourdb \
--archive "backups/yourdb-$TS.archive" \
--gzip \
--oplog
Single collection dump (filter an active subset to reduce size):
mongodump \
--uri "$RS_URI" \
--db yourdb \
--collection orders \
--archive "backups/yourdb-orders-$TS.archive" \
--gzip
Encrypt the archive at rest (example with GnuPG):
gpg --symmetric --cipher-algo AES256 "backups/yourdb-$TS.archive"
shred -u "backups/yourdb-$TS.archive" # remove plaintext safely
Retention idea:
- Keep N daily, W weekly, M monthly archives
- Store offsite copies and test restores from offsite too
Backup examples (snapshots)
Standalone mongod: lock, snapshot, unlock.
// In mongosh on the node
use admin
db.adminCommand({ fsync: 1, lock: true })
Take an LVM, EBS, or other storage snapshot at the filesystem layer, then:
// Back in mongosh
db.fsyncUnlock()
Sharded clusters: disable the balancer and snapshot each shard and config server consistently.
// In mongosh connected to mongos
sh.stopBalancer()
// Verify state: sh.getBalancerState() should be false
After you have taken synchronized filesystem snapshots on all shards and config servers, re-enable the balancer:
sh.startBalancer()
Notes:
- mongodump --oplog works for replica sets, not for a sharded cluster via mongos
- For cross-shard consistency, prefer coordinated filesystem snapshots or a managed backup solution
Restore examples
Always restore to a safe target first (staging DB or new namespace) and validate before cutover.
Basic restore of a full database archive:
export RESTORE_URI='mongodb://restore: REDACTED_STRONG_PW@primary/yourdb?authSource=admin'
mongorestore \
--uri "$RESTORE_URI" \
--archive "backups/yourdb-$TS.archive" \
--gzip \
--nsFrom 'yourdb.*' \
--nsTo 'yourdb_restored.*' \
--drop \
--writeConcern '{"w":"majority","wtimeout":60000}'
Selective restore of one collection only:
mongorestore \
--uri "$RESTORE_URI" \
--archive "backups/yourdb-orders-$TS.archive" \
--gzip \
--nsInclude 'yourdb.orders' \
--drop
Point-in-time recovery (PITR) using a dump with oplog:
- The mongodump command above wrote an oplog segment into the archive
- You can replay that oplog during restore, optionally limiting by a timestamp
# Restore base plus oplog replay to latest in archive
mongorestore \
--uri "$RESTORE_URI" \
--archive "backups/yourdb-$TS.archive" \
--gzip \
--oplogReplay
Limit oplog to a timestamp (format: seconds[:ordinal]):
# Example: stop at UNIX epoch 1719931200
mongorestore \
--uri "$RESTORE_URI" \
--archive "backups/yourdb-$TS.archive" \
--gzip \
--oplogReplay \
--oplogLimit 1719931200
Tip: restore into a different namespace (yourdb_pitr.*) so you can compare before any swap.
Validation checks after restore
Run fast, deterministic checks before you consider the restore good.
- Counts
// Compare source vs restored (run on each)
use yourdb
const expected = db.orders.countDocuments({})
// On restored namespace
use yourdb_restored
const actual = db.orders.countDocuments({})
printjson({ expected, actual, ok: expected === actual })
- Hashes (per collection)
// On each environment
db.adminCommand({ dbHash: 1, collections: ["orders"] })
- Indexes
db.orders.getIndexes()
- Representative queries
db.orders.find({ status: "open" }).limit(5).toArray()
db.orders.aggregate([{ $match: { createdAt: { $gte: ISODate("2026-01-01") } } }, { $count: "n" }])
- Users and roles
use admin
db.getUsers()
- Feature compatibility version (fCV)
use admin
db.adminCommand({ getParameter: 1, featureCompatibilityVersion: 1 })
Automated Node.js sanity script (optional):
// verify.js
const { MongoClient } = require('mongodb');
(async () => {
const srcUri = process.env.SRC_URI; // e.g. mongodb://.../yourdb
const dstUri = process.env.DST_URI; // e.g. mongodb://.../yourdb_restored
const coll = 'orders';
const src = new MongoClient(srcUri);
const dst = new MongoClient(dstUri);
try {
await src.connect();
await dst.connect();
const srcCount = await src.db().collection(coll).countDocuments();
const dstCount = await dst.db().collection(coll).countDocuments();
console.log({ srcCount, dstCount, match: srcCount === dstCount });
process.exit(srcCount === dstCount ? 0 : 2);
} catch (e) {
console.error(e);
process.exit(1);
} finally {
await src.close();
await dst.close();
}
})();
Rollback planning and cutovers
Avoid restoring straight into production. Instead:
- Restore to parallel collections inside the same database (suffix _new)
- Warm up caches with read queries
- Atomically swap collection names
- Keep the old collections around for quick revert, then clean up
Example rename plan (admin privileges required):
use admin
// Swap orders_new -> orders with a backup of the old
// Step 1: archive old collection name
db.adminCommand({ renameCollection: 'yourdb.orders', to: 'yourdb.orders_old', dropTarget: true })
// Step 2: promote new collection
db.adminCommand({ renameCollection: 'yourdb.orders_new', to: 'yourdb.orders', dropTarget: true })
Notes:
- renameCollection works within the same database
- Do one collection at a time for clear rollbacks
- If you must rollback, reverse the rename steps
Disaster recovery planning
Define targets:
- RTO: how long a restore can take
- RPO: how much data you can afford to lose
Recommended practices:
- Replication: run replica sets across failure domains
- Backups: keep encrypted, versioned copies offsite
- PITR: combine regular base dumps or snapshots with oplog archiving
- Runbooks: document hostnames, URIs, credentials handling, commands, and success criteria
- Rehearsals: run quarterly restore drills, record timings, fix bottlenecks
- Monitoring: alert on backup job failures, data drift, and storage capacity
Local Pilot Plan
Start small and measurable before you scale out:
Scope
- One database (yourdb), one critical collection (orders), target size under a few GB
Steps
- Take a mongodump with --oplog from a replica set secondary
- Restore into yourdb_restored on a dev cluster
- Run the validation script (counts, hashes, sample queries)
- Record dump size, dump time, restore time, validation results
- Document findings and optimize where needed (compression, parallelism, indexes)
Exit criteria
- Restore passes all checks
- End-to-end time is under your RTO budget for the pilot size
- Team members can repeat the process without help
Common mistakes and fixes
Fix: Schedule monthly or quarterly restore drills with validation.
- Mistake: Not testing restores.
Fix: Always add --oplog when using mongodump against a replica set.
- Mistake: Skipping --oplog for replica sets.
Fix: Use coordinated filesystem snapshots with the balancer stopped, or a managed solution.
- Mistake: Backing up a sharded cluster via mongos expecting cross-shard consistency.
Fix: Restore into a staging namespace, validate, then cut over with renames.
- Mistake: Restoring directly into production.
Fix: Compare getIndexes and db.getUsers outputs.
- Mistake: Forgetting indexes or users in validation.
Fix: Read from a secondary with --readPreference secondaryPreferred.
- Mistake: Overloading the primary during backup.
Fix: Encrypt at rest, restrict access, and rotate keys.
- Mistake: Unsecured backup artifacts.
Conclusion
You now have a practical MongoDB backup and restore workflow:
- For replica sets, use mongodump with --oplog and restore with validation
- For sharded clusters, prefer coordinated filesystem snapshots
- Validate counts, hashes, sample queries, indexes, and users
- Plan rollbacks with collection renames, and rehearse DR to prove RTO/RPO
Next steps
- Implement the Local Pilot Plan this week
- Automate validation and add monitoring for failures
- Expand scope and frequency once the pilot is green