Every owner eventually asks the same question about a platform: if something goes wrong, can we get back? On a mature WordPress host the answer is usually a nightly backup and a restore button. On emDash, the honest answer is "yes, if you prepare for it" — and preparing for it is cheaper and quicker than most teams expect.
This piece sets out how we made emdashnow.com recoverable before launch: how database changes reach production without surprises, what a real backup of an emDash site contains, and how the key that protects stored credentials can be changed without breaking anything. Each procedure was rehearsed before it was needed. The detail is here for the people who will run a site; the decisions are here for the people who pay for one.
What "recoverable" means on emDash
A WordPress backup plugin usually bundles the database and the uploads into one file. emDash separates the pieces, and each needs its own plan. If one is missing, a restore is incomplete.
| Piece | What it holds | How we protect it |
|---|---|---|
| The database | Content, users, settings, plugin data and subscriber records | Point-in-time recovery, plus a weekly encrypted copy stored outside Cloudflare |
| The media | Images and files, stored separately from the database | A copy of the storage bucket, restored only into an empty bucket |
| The encryption key | Unlocks credentials that plugins store, such as email and anti-spam keys | Held offline in two places; never inside any backup |
| The site's code | The exact version that matches the database | Version control, with every production deploy tagged to its commit |
Database changes without surprises
emDash upgrades its own database tables from time to time, through what it calls core migrations. By default, a newly deployed site applies any pending migration on the first request it receives. That is convenient, and it means the first visitor after an upgrade triggers a change to the live database, with no recovery point recorded beforehand.
emDash supports a more careful arrangement, and we adopted it in stages. Each deployment now runs a fixed sequence, and any failure stops it.
The database's current point-in-time bookmark is written to the deployment record before anything changes.
The deployment confirms it is pointed at the production database, and lists pending and unexpected migrations. Anything unexpected stops it.
Pending migrations run as a deployment step, only when the recovery point exists and the database matches the one reviewed in advance.
The new version of the site goes live only after its database is ready.
A request to the live site and a migration check confirm that nothing is pending. A failure here is reported at once.
The live site is set to check rather than apply: if a migration is somehow missing, it refuses to serve with a clear error instead of changing its database mid-request. We moved to that setting after five clean deployments with the new steps, which is the gradual path emDash's own documentation recommends.
The final check earns its place. On one deployment, a code change that expected a new content field reached production before the field itself had been added. The home page, which reads it, stopped loading. The post-deployment check flagged the failure immediately, the field was added, and a small fix made the code tolerate the gap in future. Without the check, the first report would have come from a reader.
Two related points matter for planning. Rolling back the site's code does not roll back its database, so a rollback after a migration means restoring both together. And changes to your own content model — new fields and collections — are separate from emDash's core migrations and need their own step in the deployment.
Backups that actually restore
On Cloudflare, emDash stores its database in D1, which keeps a rolling history called Time Travel. Any moment in the retention window can be restored: 30 days on the Workers Paid plan, 7 days on the free plan. It restores content, users, settings, plugin data and the migration records in one action, and it is the first thing to reach for.
Thirty days is not always enough, and a backup held by the same provider as the site does not protect against losing access to that provider. So we added a second layer: a weekly copy of the whole database, encrypted before it leaves the build server and stored with a separate provider, in a bucket that the backup job can write to but not read, list or delete. Old copies are kept for about twelve weeks.
| Layer | Covers | Retention | Restores |
|---|---|---|---|
| Time Travel | Mistakes, failed migrations, accidental deletions | 30 days (Workers Paid) | The database in place, in about 20 seconds in our rehearsal |
| Weekly encrypted dump | Longer look-back, and loss of the Cloudflare account | About 12 weeks | Into a new, empty database, verified before switching |
| Media bucket copy | Lost or overwritten images and files | As long as the copy is kept | Into a new, empty bucket |
Building the second layer surfaced a trap worth knowing. Cloudflare's standard database export refuses to run on an emDash site that uses search, because emDash keeps its search index in a kind of table the export cannot handle. The fix, which emDash's documentation now describes, is to export every other table and let the site rebuild its search index on the first search after a restore. A second trap: very long entries can produce single statements larger than D1 will accept on import, so each dump is checked for them when it is made.
Restores follow one rule without exception: never restore over the live database. A copy is restored into a new, empty database, checked, and only then connected to the site.
Changing the key that protects stored credentials
Plugins store credentials — the email provider's key, the anti-spam secret — encrypted in the database. The key that encrypts them lives outside the database, as a platform secret. That separation is good security, and it has two consequences an owner should understand. A database backup without the matching key cannot unlock those credentials. And changing the key, which good practice calls for periodically or after a suspected exposure, must be done in an order that never leaves a credential unreadable.
emDash makes this possible by accepting a list of keys. New values are encrypted with the first key in the list; older values are read with whichever key wrote them.
The site now writes with the new key and can still read everything written with the old one.
Each one is re-encrypted with the new key. emDash does not yet report which values still use an old key, so keep a list and work through it.
Run the site with the new key alone, check every integration that uses a stored credential, and only then retire the old key.
We rehearsed the full sequence on a preview deployment. Every stored credential moved to the new key, sign-ups and form protection kept working at each stage, and the procedure for production is written down step by step.
The rehearsals
The rehearsals ran on the preview database, never on production. The numbers are small because the site is young; what matters is that each procedure was proven end to end, and that the written instructions are the ones that worked.
The recommendation from user persona point of view
- Running a WordPress site that works. Recovery is not a reason to stay, or to go. emDash's tools are sound, but they are not one button. Before any move, ask who will own the four pieces above and when they last tested a restore.
- Commissioning a new site. Make recoverability a launch requirement: a recorded recovery point before each deployment, an off-platform copy, the key stored separately, and a rehearsal you can see. It adds a few days to a build and removes a category of risk.
- Running an agency. A rehearsed recovery plan is a deliverable clients rarely ask for and always value after an incident. Package it as part of every launch, alongside the wider emDash security checklist. The rest of the build is described in what we learned running emDash in production.



