Backup and restore
Operator page
Developers and owners only. Restoring replaces everything. There is no undo.

Export
Press Download backup. You get a single file containing the whole production database. It is the same file whichever environment the console is showing.
When the server has a backup key, the button says Download encrypted backup (.dump.age) and the card names the key (age1…). That file opens only with the owner's private key, on their own computer. Without a key the card warns in red that copies leave the server unencrypted.
Keep the encryption key with the backup
Stored Stripe and Mailchimp keys inside the backup are encrypted. Without the application's encryption key the backup restores, but every connection in it is unreadable. The key is not in the file.
Restore

Choose the file and press restore. It takes the plain .dump. An encrypted (.age) or zipped (.gz) copy is refused with the step that is missing: open it on your own computer first, as below. If the database already has data, FMS refuses and asks you to type REPLACE in full. That is the last thing standing between you and losing everything currently in the system.
A restore always replaces the production database. The card counts production's members, whichever environment is active. While a sandbox is active the card says so and the restore is refused with "Switch to production before restoring". Switch on Settings → Sandboxes first.
What comes back, and what does not
A restore brings back every member, plan, team and connection as the backup holds them. It also brings back the staff accounts, passwords, two-step set-ups and service keys of that day.
Changes to sign-ins made after the backup are put back by FMS straight after the restore:
| Made after the backup | After the restore |
|---|---|
| A colleague switched off | Still switched off |
| A password changed, or a temporary password set | The newer password |
| Sign out my other sessions | Those sessions stay signed out |
| Two-step sign-in reset, or a new phone | The newer set-up |
| A recovery code used, or a new set made | Still used, or the new set |
| A service key revoked, or a website switched off | Still refused |
FMS never switches off the last owner who can sign in. If the backup would leave nobody else, that owner stays on and is signed out everywhere. The success message names them. Check them on Settings → Staff.
A brand-new server starts without that record
Restoring onto a new server brings the backup's sign-ins back unchanged. Switch off colleagues and revoke keys again by hand if you did so after the backup.
A restore also ends every Claude connection the backup carried, so one that was ended since cannot come back. Colleagues who use Claude with FMS press Connect in Claude and sign in again (Connect Claude to FMS). The success message says any Claude connections in the backup were ended.
Two things can go wrong after the data is back, and they are reported apart. If FMS could not end the Claude connections, the restore shows an error: ask the technical team before you let anyone use Claude. If only the read-only access Claude uses could not be set up again, the restore succeeds and shows a warning: "Claude cannot read FMS until … run ops/ai-reader.sql or restart FMS". Nothing else is affected. Pass the warning to the technical team.
Off-site copies (Cloudflare R2)
The middle card sends a copy of the database to a Cloudflare R2 bucket every night and keeps the newest 7 daily and 12 weekly copies. You need four things from the Cloudflare dashboard: the account ID, the bucket name, and an API token's access key ID and secret (scoped to that bucket, Object Read & Write).
- Enter the four values and press **Connect bucket**. FMS checks them with Cloudflare first; a wrong token is refused and nothing is stored.
- Press **Upload snapshot now** once. The card shows the file name that landed and when.
- Leave it. Every night at the hour shown (03:00 UTC unless you change it) a new copy goes up. **Health** raises an alert if a night is missed.
Test connection lists what is in the bucket. Edit lets you change the schedule or rotate the token — leave the secret blank to keep the stored one. Remove forgets the target; nothing in the bucket is deleted.
One uploader per bucket
If the server's .env also has R2_* values for the separate backup container, blank them. Two uploaders pruning the same folder keep fewer copies than you think. Health turns the Off-site backup row amber while both are set.
If no target is set at all
On the live console, Health shows Off-site backup in amber with not configured. The only copies are then on the same server as the database. Connect a bucket here to clear it.
A custom endpoint must start with https://
The live console refuses an http:// endpoint. The upload holds every member's record. Plain http:// is only allowed on a test machine, or for a store on the server itself (localhost), where nothing crosses a network.
Encrypted copies
Every copy that leaves the server, the nightly R2 upload, the download and the separate backup container, is encrypted to a key pair the owner makes once. The server holds only the public half. Someone who takes over the server cannot read the copies, and nobody can restore one without the private half.
- On your own computer, install age and run
age-keygen -o fms-backup-key.txt. It prints a line startingPublic key: age1. - Save
fms-backup-key.txtin the password manager, and one copy offline. Tell a second person where it is. - Whoever runs the server puts the public key in
.envasBACKUP_AGE_RECIPIENTand restarts FMS. - This page then names the key, and the download button says encrypted.
Lose the private key, lose the backups
An encrypted copy cannot be opened without fms-backup-key.txt. FMS never sees that file and cannot make a new one for old copies. Never put it on the server.
To open a copy before a restore, on your own computer:
bash
age -d -i fms-backup-key.txt frontier-backup-<date>.dump.age > backup.dump
age -d -i fms-backup-key.txt fms-<date>.dump.gz.age | gunzip > backup.dump # from R2Then upload backup.dump here. The server's restore script opens it by itself with --identity fms-backup-key.txt.
Test that a restore works
Do this every three months, and after any change to the backup set-up. It proves the newest copy can be fetched, that the private key opens it, and that it restores with the same contents. It never touches the live database: it restores into a scratch database and deletes it afterwards. Whoever runs the server follows §5.1 Restore drill in ops/RUNBOOK.md; the owner brings the private key file.
The retention rules are written up in ops/RUNBOOK.md §4.