The tool MongoDB built for moving a live cluster has a paragraph on its limitations page worth reading before anything else. MongoDB "does not test Mongosync with Community builds and in most cases, MongoDB does not offer support for Mongosync with Community deployments." If you are leaving MongoDB Atlas for a replica set you run yourself on Community Edition, that sentence is about you, and the page's advice is to call a sales representative.
Atlas has no region in Kenya or anywhere in East Africa. Its only African regions, on all three clouds it runs on, are in South Africa: Cape Town on AWS, Johannesburg and Cape Town on Azure, Johannesburg on Google Cloud. So when the Data Protection Act is the reason, a MongoDB Atlas migration from Kenya runs outward, off the platform and onto servers in the country. CloudSpinx moves MongoDB off Atlas and then runs the replica set on infrastructure in Kenya through our managed database service, which is why we ran mongosync 1.22.0 between two MongoDB 8.0.34 Community replica sets with an application writing through the cutover, then broke it on purpose. Neither end was Atlas. The Atlas facts below come from MongoDB's documentation, and every timing comes from our lab.
Can mongosync move you off Atlas at all?
Only if four checks pass, and the cluster you already have decides three of them.
The tier comes first. The mongosync limitations page rules out the shared tier, serverless clusters and Atlas Infinite clusters in one line each, and does not mention Flex either way. A Free cluster has no snapshot download, so its offline route is mongodump. Flex and dedicated clusters can also download a snapshot as raw data files you start mongod on. Both are offline copies, only as fresh as the moment you took them, so the application stops for the whole copy: fine for a small cluster, a long night for a large one.
Then the version. mongosync 1.22 reads a source on 5.0, 6.0, 7.0 or 8.0, writes to 7.0 or 8.0 in the pairs its table allows (an 8.0 source goes to 8.0 only), and does not support rapid or minor releases such as 8.1, 8.2 and 8.3. On Atlas that bites, because a dedicated cluster on the "Latest Version With Auto Upgrades" release option receives the latest available MongoDB version, minor releases included, and MongoDB 9.0 is already out. We pointed mongosync at a 9.0.4 source:
$ curl -s localhost:27384/api/v1/start -XPOST --data '{"source":"cluster0","destination":"cluster1"}'
{"success":false,"error":"ClusterValidation","errorDescription":"source and destination
version compatibility check: The source version: 9.0 to destination version: 8.0 is not supported"}
A 9.0 destination got the same refusal with 9.0 in place of 8.0. If the cluster page in Atlas shows 8.2, 8.3 or 9.0, this release of mongosync has no route for you, and the plan is an offline copy with the application stopped. Free and Flex clusters never had a say: MongoDB's versioning page says they are upgraded automatically, with no release option to choose.
Third, collection types. Time-series collections are on the unsupported list. The page warns that mongosync "does not check for compliance with the documented limitations", but 1.22 does check for this one. Our source carried one with 10,000 documents, and /start answered "iot.metrics is a timeseries collection, which mongosync forbids: unsupported collection" before the process exited. Clustered collections with a TTL sit on the same list.
The fourth check is who your application connects as, and it only shows up at the cutover.
- another route
- refused, or silently wrong
- live sync
A no at check 1 or 2 means an offline copy, a dump or a downloaded snapshot, with the application stopped for the whole copy. A no at check 3 means that collection goes by dump in the stopped window, and the rest live-syncs with nothing blocking the source.
What the application sees at cutover
The source held 500,000 orders with a unique index, a TTL collection, a view, two application users and a custom role. A writer inserted and updated at 44.6 operations a second as an ordinary readWrite user. The initial copy of about 120 MB finished six seconds after /start, over loopback, which tells you nothing about a link from Cape Town, and canCommit turned true at 18 seconds.
Under steady writes the lag never settled. Across 165 samples over 85 seconds overallLagSeconds swung between 1 and 6, averaging 3.4, and zero never showed up once, so a script waiting for zero before committing would still have been waiting. MongoDB's commit page says to stop your application first. We left it running on purpose.
"User writes blocked, reason: Unspecified"
That is error 371, UserWritesBlocked. mongosync's own log has the block switched on 72 milliseconds after the commit call, and the next insert our writer sent came back with it. Two writes landed before the block, at 21 and 44 milliseconds, and both reached the new cluster. All 964 attempts over the next 21 seconds failed, each in under 8 milliseconds. The new cluster's block came off 5.56 seconds after the commit call, and mongosync logged the commit complete at 6.06.
- new cluster not yet writable
- new cluster writable
- failed, restart from scratch
The old cluster keeps refusing the application's writes until somebody runs the unblock command. Both clusters sat on one machine with 500,000 documents and about 44 writes a second, so the seconds will not carry over to a real link.
Every acknowledged write arrived: 2,406 of 2,406 inserts and 2,406 of 2,406 updates, checked one by one, and the embedded verifier hashed 502,406 documents on each side before the state moved to COMMITTED.
The block holds only against users without the bypassWriteBlockingMode privilege. On a self-managed cluster the built-in root and restore roles carry it, and our admin user, holding root, inserted into the old cluster after the commit with no error at all. That document stays on the source, and nothing carries it across. On Atlas, a custom role can grant the same privilege under the name BYPASS_USER_WRITE_BLOCK. Whatever your application connects as, check what its roles carry before the night, not on it.
The old cluster stays blocked after COMMITTED, and that is your rollback. Our application user was still refused until somebody ran this:
db.adminCommand({ setUserWriteBlockMode: 1, global: false })
The next insert went straight through. Put that line in the runbook next to the name of whoever is allowed to run it. If you would rather see these timings on a copy of your own data than on ours, that rehearsal is the first thing we scope through the migration form.
What came across, and what stayed behind
The data, the four indexes on orders, the TTL and the view arrived intact. During sync mongosync keeps unique indexes non-unique and parks TTL expiry at the maximum integer; after the commit, orderNo was unique again and expireAfterSeconds was back to 3600.
The users did not come across, and neither did the custom role. The limitations page says "mongosync doesn't sync users or roles", and our destination's shop database had none of either after the commit. Create them on the new replica set before you start, because the first thing your application does after you repoint it is log in.
Atlas Search indexes are on the not-synced list too, so a cluster that uses search needs that settled before it gets a cutover date.
mongosync also leaves something of its own. A database named __mdb_internal_mongosync, eight collections and 4,601 documents in our run, sits on the destination after COMMITTED. It is small, 420 KB here, and MongoDB's behavior page says to drop the metadata databases after a successful migration.
One sizing number did not match our run. The limitations page says the verifier needs "approximately 10 GB of memory, plus an additional 500 MB for every 1 million documents", and our mongosync process peaked at about 1.3 GB on half a million. Size the migration host to MongoDB's figure anyway: a process killed for memory during the commit means starting the migration again.
Excluding a collection switches off the safety catch
The obvious way past a time-series collection is to leave it out with excludeNamespaces and copy it separately. MongoDB's behavior page has a table that makes this costly: with a namespace filter, mongosync blocks writes on the destination but not on the source.
So we ran it with the writer still going. The commit call returned success, and 4.3 seconds later mongosync exited with this:
the source cluster was modified after Mongosync final verification began at {T:1792492464 I:26};
saw `insert` event on shop.orders (...) at {T:1792492464 I:27}
The verifier failed the migration on the first insert that arrived after its final pass began, one of 193 writes the source accepted in those 4.3 seconds. The writer never saw an error, because the source accepted all 10,284 of its writes after the commit call. MongoDB's commit page says a sync that stops during commit, before canWrite turns true, has to start over from the beginning. That was cheap on our 120 MB. On a cluster big enough to need a live sync, it costs you the night.
With a filter, stopping the application is your job alone, and nothing enforces it. We did not re-run it with the writer stopped. An earlier filtered attempt, in which every write failed or changed nothing, did reach COMMITTED, and we kept it out of the numbers. The same table leaves the source writable in two other cases, a source older than 6.0 and a destination that already holds data, so a 5.0 cluster commits with no safety catch at all.

The order we cut over in
- Read the tier and version on the Atlas cluster page. A dedicated cluster on 5.0 to 8.0 can live-sync to a destination the compatibility table pairs it with; anything else gets an offline copy with the application stopped.
- List collection types with
db.getCollectionInfos(), and move time-series and clustered TTL collections by dump in that same stopped window. - Create the users and roles on the new replica set before the first sync.
- Confirm the application's database user carries no
bypassWriteBlockingMode: norootorrestoreon a self-managed cluster; on Atlas, no custom role withBYPASS_USER_WRITE_BLOCK, and check what its built-in roles grant. - Give the host running mongosync the memory MongoDB asks for, not what our run used.
- Stop the writers, call
/commit, and repoint the connection string only oncecanWriteis true. - Leave the old cluster blocked until the new one has carried real traffic, with the unblock command written down for whoever is on call.
Who should skip a MongoDB Atlas migration
If nothing obliges your data to sit in Kenya, Cape Town is a reasonable place for it and Atlas runs a replica set well. Leaving hands you the backups, the upgrades and the pager. Whether a rule really requires the move is a question about the instrument, not the database, and where servers can legally live in East Africa works through the Kenyan ones. The missing region is the same gap any cloud design for Kenya starts from: no hyperscaler region in the country, so residency means hardware here.
If the rule does apply, send the Atlas tier and MongoDB version, the data size, the collection types from db.getCollectionInfos() and how many database users you have, through the managed database scoping form or on WhatsApp at +254 713 403 044. We tell you which of these routes fits your cluster, the cutover order for it and one figure for the move. The reply costs nothing and commits you to nothing. If it goes ahead, we run the move with you: a rehearsal on a copy of your data with the timings written down, then the cutover in a window you pick.