Mastodon Backups & Data Independence

Your community's data, in your hands

Ask about backups

📖 In plain terms


A backup is a complete copy of your community's server. Ours is locked with a key that only you hold, and kept in storage that only you control. So even if toot.io disappeared overnight, your community's posts, follows, and media stay safe — and you could rebuild somewhere else without us.

  • ✅ It's yours — stored in your own account, not ours.
  • 🔒 It's private — locked with your key; nobody else can read it, not even us.
  • 🧪 It's proven — a bundled tool checks the backup really works, before you ever need it.
How a backup works, in three steps We copy your server every night, lock it with your key, and store it in a place you own. 🌙 We copy your server every night, automatically 🔒 We lock it with your key only you can open it 📦 It lands in your storage a place you own

🤝 Why we think independent backups matter


You shouldn't have to trust any single provider. Not even us!

We've been running Mastodon instances since 2018 and we take reliability seriously. We back up your instance daily. But even the most dependable infrastructure can have incidents, and companies change. We think your community's data should be safe independently of what happens to us.

Backups you control can be restored without our involvement

Every backup we produce is encrypted with your own public key using age, an open-source encryption tool. We never hold your private key. The backup lands in your own S3 bucket. A complete restore needs nothing from us. Not our software, not our credentials, not our cooperation.

Tested backups are better than untested ones

Discovering a backup is corrupt or incomplete is something you want to learn during a routine rehearsal, not during an actual emergency. Every backup we produce includes a one-command verification harness: a Docker-based tool that decrypts, loads, and verifies your data from start to finish. Run it quarterly or whenever you need reassurance.

🛡️ Disaster recovery backup


A full, encrypted snapshot of everything needed to bring your Mastodon instance back to life on any infrastructure, from scratch, with no dependency on toot.io.


🔒 Encrypted with your key before it leaves our servers

Your data is locked before it ever leaves us — unreadable to anyone but you, including us and your storage provider. Think of it as a padlock only your key opens: you hand us the open padlock (your public key), we snap each backup shut, and only your private key can reopen it. Technically, each artifact is encrypted with age using the public key you provide, and the locked-up ciphertext is the only thing that travels to your S3 bucket. Backup metadata and documentation (the manifest and the restore guide) are left unencrypted so that you can inspect them easily. They hold only filenames, sizes, checksums, and version numbers — no secrets and none of your users' data — so even a misconfigured, publicly readable bucket would leak that metadata but never the backup itself.

📦 Stored directly in your own S3 bucket

Think of it as a storage locker you rent in your own name. Backups go to cloud storage you control that follows the common S3 standard — OVH, AWS, Hetzner, Backblaze, or many others. They live in your account, under your access controls, independent of our infrastructure.

🗄️ Complete: database, secrets, and important media

Every backup includes: the full PostgreSQL database (custom dump format, restores with a single pg_restore command), your sanitized .env.production with all secrets intact (SECRET_KEY_BASE, OTP_SECRET, VAPID keys, ActiveRecord encryption keys), and all instance local media (attachments, avatars, custom emojis, and site uploads). User requested backup dumps, imports, and cached remote instance media are not included.

📋 Bundled restore runbook

Each backup includes a RESTORE.md: a step-by-step production restore guide generated specifically for that backup. Version pins, exact artifact filenames, SHA-256 checksums, and every command you need are embedded directly. In a crisis, you open one file and follow it.

🔐 SHA-256 checksums on every artifact

A manifest.json is written last. It lists every artifact with its exact size and SHA-256 checksum. The manifest's presence means the backup completed fully; a partial backup has no manifest. Before decrypting anything, the verification harness verifies every checksum automatically.

⏱️ Your backup schedule, your backup retention

You want daily disaster recovery backups? No problem. Only want them weekly? Sure. Just tell us on which schedule we should push backups to your bucket.

We only ever upload new backups to your S3 bucket — we never delete anything. In fact, the credential you hand us only needs to add objects: it cannot read, delete, or list your backups. See which permissions we need and how to set up automatic retention below.

🚛 Moving away from toot.io


In case you want to leave toot.io and want to move your instance, we will provide you with a complete backup of your instance's data. Including the redis database and all cached media, that we skip for the normal disaster recovery backup above.


📂 Standard, open formats throughout

The database is a standard PostgreSQL custom-format dump, compatible with any recent Postgres version. Media archives are plain tar files in handy 100GB chunks. Secrets are a text file. The Redis database is a standard Redis RDB dump. No proprietary format, no special tool from us required to read or restore your data.

📦 Your storage or ours?

We can push the migration backup to the S3 bucket you specify, or we give you links to download the backup files (valid for 24 hours). In both cases the files are encrypted with an age key that you provide.

🧭 Everything needed to set up elsewhere

The bundled RESTORE.md walks you through the migration process. The included verification harness can verify you got everything intact and that the backup is consistent.

👋 The process

When you decide to leave us for somewhere else, contact us and we will come up with a time frame for the migration. The migration will involve shutting down your Mastodon instance at our place to ensure that the migration backup we provide is consistent and complete. After the shutdown, we hand it over as described above (your bucket, or download links valid for 24 hours). Depending on the size of your instance, the migration backup can take from a few minutes up to a few hours to complete — the network connection between our servers and the S3 bucket typically sustains 30–60 MB/s.

✅ The verification harness


Think of it as a fire drill for your backup: you practice the restore so you know it works, long before a real emergency. Bundled with every backup is a self-contained tool that decrypts your artifacts, loads your database into a throwaway Docker environment, runs migrations, and verifies that your encryption keys and secrets are all correct. Run it any time you want proof that your backup actually works.


🐳 One command, no special setup

The verification runs with only standard tools you already have or can install in minutes: the age CLI, Docker, and Docker Compose. No toot.io software, no account access, no API keys. Run it from your laptop or a dedicated ops machine:
./verify.sh -i your-age-identity.txt -d ./downloaded-backup/

🔌 Fediverse-isolated test

The verification stack runs on a Docker network with no default gateway, so it has no internet egress. It never spins up your full Mastodon instance — and even if it did, it could not reach out and impersonate your instance in the Fediverse.

🙋 Answers to some questions you may have


Do I need to be technical to use this?

To set it up: not really. You create a storage account and an encryption key — we give you the exact steps, or we can hop on a call and do it together — and we handle everything else. After that, backups just happen on the schedule you choose.

A real restore is more involved, but you are never stuck: every backup ships with a step-by-step RESTORE.md that you — or any sysadmin you hire — can follow, and we are happy to help. The whole point is that your data is always recoverable, with or without us.

Does it cost anything?

We charge nothing for setting up the disaster recovery backup, or for exporting your data when you move away from toot.io.

You only pay your object storage provider for the space the backups use — that bill goes to them, not to us. Any S3-compatible storage works (we have only tested OVH and iDrive, but others should be fine); pick an inexpensive provider.

Ballpark: roughly $10–15 a month to your storage provider for a typical mid-size instance — often less. Here is the detailed math:

A back-of-the-napkin example: say your database is 80 GB and you have 200 GB of local media. The database compresses with zstd (and indexes are not part of the backup) down to roughly 5–10 GB, so one backup is about 210 GB. With weekly backups kept for 8 weeks, you store up to 8 copies — about 1,680 GB. At OVH's rate of $0.0081103/GiB/month that works out to roughly $14/month (~$165/year). Your actual cost depends on your instance size, your retention, and your provider.

The backup traffic also counts towards your instance's bandwidth usage.

That… sounds… complicated! Do I need that?!

We do understand: age encryption, object storage, backup retention policies and the like sound like complicated stuff. And shopping for and paying for object storage is a bit of a hassle. So to answer your question: No, you don't need it if you are happy with toot.io's daily backups and the fact that you have to trust us to keep your data safe. But if you want to be independent of us, and have a backup that you can restore without our involvement, then this is the way to go.

How big is a backup and how long does it take?

This depends on the size of your Mastodon instance and the network speed to your chosen object storage. We stream the backup directly to your S3 bucket at roughly 30–60 MB per second — fast enough that even a large server usually finishes in under an hour.

Two things keep the transfer small in the disaster recovery case. First, the PostgreSQL dump compresses heavily with zstd — a 200 GB database typically shrinks to 10–20 GB. Second, we only back up your local media (attachments, avatars, emojis, uploads), not the cached copies of remote media that make up the bulk of most instances' media directory. So an instance with a 200 GB database and 500 GB of media on disk might only ship ~10–20 GB of database plus well under 100 GB of local media — roughly 100 GB in total, not 700.

Is the backup consistent?

The database backup is a consistent snapshot of the database (pg_dump). For the disaster recovery backup, the media files are backed up after the database dump and while Mastodon is running. So there might be some inconsistencies in the media files in this case. For export backups, we stop your instance before backing up, so that you have a truly consistent state.

How do you handle S3-based Mastodon media?

Some of our customers brought their own storage/bucket for Mastodon media and we have configured their instance to use that. (Amazon S3, Azure Blob Storage, Google Cloud Storage, etc.) We will not back up these. For each backup we need to download the media from the object storage provider and upload it to another object storage provider. This might be quite costly (depending on your provider) and is not needed - just use your media as is, when you restore from our backup.

For many instances we manage the S3-compatible media storage for you. In this case, we will back up the media.

How do I set up the backup retention policy?

That is also something that changes depending on your object storage provider.
Most object storage providers have a way to automatically delete old backups after a certain amount of time.

On Amazon S3 (and most S3-compatible providers) a lifecycle configuration can be applied once with the CLI: aws s3api put-bucket-lifecycle-configuration --bucket YOUR_BUCKET --lifecycle-configuration file://lifecycle.json. This example expires backups after 8 weeks and also cleans up any interrupted multipart upload after 7 days:

Click to see the lifecycle configuration
{
  "Rules": [
    {
      "ID": "expire-old-backups",
      "Filter": { "Prefix": "backups/" },
      "Status": "Enabled",
      "Expiration": { "Days": 56 },
      "NoncurrentVersionExpiration": { "NoncurrentDays": 56 },
      "AbortIncompleteMultipartUpload": { "DaysAfterInitiation": 7 }
    }
  ]
}

Versioning & write-once. If you turn on bucket versioning, a new upload never overwrites an older backup — each one becomes its own immutable version. Paired with the write-only key from the permissions section (and, if you want a hard guarantee, S3 Object Lock in compliance mode), this gives you write-once-read-many (WORM): backups can be added but not altered or deleted before they expire — protection even against a leaked key or an accidental delete. With versioning enabled, the NoncurrentVersionExpiration rule above is what ages out the old versions.

Storage classes. Disaster-recovery backups are written once and almost never read: the ideal case for colder, cheaper storage classes. Our uploader does not currently set a storage class at upload time, so use a lifecycle Transition rule if you want to move backups to a colder class. One caveat: the colder classes bill a minimum storage duration, so they only pay off when you keep backups for many months. For short retention like the 8 weeks above, the default class is usually the cheapest option.

What permissions does toot.io need on my bucket?

To store a backup we only ever need to add new objects under one prefix in your bucket. We never read your backups back, we never delete them, and we never list your bucket. So the cleanest setup is a write-only key that physically cannot do any of those things. You don't have to: a normal read/write key works fine and we simply won't use anything beyond the two actions below.

Our uploader (AWS SDK for Go v2, multipart) needs exactly:

  • s3:PutObject — stores each artifact. On AWS this single action also covers the whole multipart machinery we use for the large database dump (create / upload-part / complete).
  • s3:AbortMultipartUpload — lets us tidy up a half-finished large upload if a run is interrupted, so no orphaned parts linger (and get billed) in your bucket. A successful backup never calls it; it only matters on failure.

That is the complete list. No s3:GetObject, no s3:DeleteObject, no s3:ListBucket. On AWS, a least-privilege policy scoped to a single backups/ prefix looks like this:

Click to see the IAM policy
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "TootioBackupWriteOnly",
      "Effect": "Allow",
      "Action": [
        "s3:PutObject",
        "s3:AbortMultipartUpload"
      ],
      "Resource": "arn:aws:s3:::YOUR_BUCKET/backups/*"
    }
  ]
}

Other providers express the same idea differently — OVH, Backblaze and friends use bucket policies or scoped application keys — but the principle is identical: a key that can only write into your backup prefix. And if you want a hard guarantee on top of that, enable object-lock / WORM so existing backups cannot be altered or removed at all, by us or by anyone holding that key.

Why age encryption? Why S3-compatible storage?

Why encrypt at all? A backup is the single most sensitive copy of your community that exists: your entire database (every post, DM, follower list, email address, and password hash), your server's secret keys, and all your media — bundled together and handed to a third-party storage provider. That provider could be breached, could be compelled to hand data over, or a single wrong setting could make a bucket readable by the whole internet. Encrypting the backup before it ever leaves our servers takes all of that off the table: the provider — and anyone who ever gets hold of the files — sees only unreadable ciphertext, and only your private key can open it. It is the same principle as the rest of this page: don't make yourself trust anyone you don't have to.

Why age specifically? It is a modern, open-source encryption tool that is simple, fast, and secure. It is built to be hard to misuse and has a deliberately small attack surface, with mature implementations across many languages — so you are not betting your backups on one tool from one vendor. We looked at GPG/PGP too, but age was the better fit: it has a native Go implementation, so we simply wrap the upload stream with the age writer and we are done — no shelling out, no fragile key handling.

Why S3-compatible storage? The S3 API is the de-facto standard for object storage, supported by practically every provider out there. That keeps you in control and free of lock-in: you can pick a provider that fits your budget, your jurisdiction, and your values — AWS, OVH, Hetzner, Backblaze, or a self-hosted Garage — and switch later without changing how the backups work.

Sounds great! What do I need to have this?

You will need to get us the following:

  • The object storage provider's endpoint URL
  • Your object storage provider's API key (ideally a write-only one — see which permissions we need)
  • Your object storage provider's secret key
  • The name of your object storage bucket
  • The name of your object storage region
  • For automated disaster recovery backups, tell us how often you want backups to be taken and when they should be taken (it is not really resource intensive besides the outward bandwidth, but scheduling while your instance is not that busy is a good idea nonetheless).
  • The public key of your age encryption key

You can encrypt the secret key and the age encryption key with age and send us the encrypted versions. Use one of the following age public keys to do so. The post-quantum key (the ~2000 character long key) is for newer age versions and for the security-conscious user. Please mention which public key you have used.

Click to see post-quantum age key
age1pq1cg6cgjep5fnnx7739yt3wmrw80fjrv5cjg7lvrac5ejvpcnym6jkc3tuc3wy2e8gwxxaw5rnmxqyq8zhtfz02ltxq2xvr7c7zd7r43c89xgjkgtksf0ua8rk0wtqnjjksjpe26jplwr34vqm0wv69806wt4hjz3s6axc3du4vu9j6vmxjsc3wv2c99nqjuu2jq62gx6v0a8xyhtwqf8svwdvslqqpxpqhla29snjxph2yduxr2p48mkv5z0hsxapvwllyck9svpfshznwhf0e9yg6y2kwpcvgd2qp2ehtv4yxgfa0q9rsqs3zevk4u9ty6jlc3xn257fzy5tp932mehytse2gsgz6ftafqjkn0stvg0xtp99wrnlcjltaq3nexpnlg4ve7s9rt87yy54lktt34uvcqqhrwkjk848a3w52da9hnrvlfy2vklc334re9pswgaetz5vguem0dd8dx7gf9rlmz2smp6x9vwf09me9ry58sr6uup3h5s8w39tvprjpzmqjt9f533urmm8vvv8fq4nqdxqrpxdx9aw2sasawv33qqmcztx32ysy0pc6679vrjs9f5f9tkhev04x3nw4hqky0qce86mjw0g53lm4azagufj55kkta9hws5gg2egx0rp5tf57vfrd2nvkd42ykfun6cfc73827nydnuzq8l9fdk6lsmrwfgq6klqhvsmg8cgc3qq9x6jnzj8kxljgxhfvmya6uvct3m4f95zn7j3p8n9dgj77k74w2ttusjchfr39njxsveemwmvdqeefje3swjf8azxxjt4x6qte2rhegm6m5kjjw9lytyq6kpqs4cl0ep87unpnmyqqa39tfc42ev4a9m3fnz2cpvetgsdqlpxrkma77wv8lv3p45pshq4mw70sfm8lnthgkfhctu8j9we7p6mskj6ypmnu5fry2r5eq8msgr90757zephq52p8u3ye88rpw30ystt2pvymmqzt0cvg20uvvpjgp9hlnpknnxyyntnr82htsjkc29t0xpew3s8zgstdfluhypsa92xdscukg3q0f7lk4hvj7770yqmflvx022zjrrpwzsawwgvmd35c5erq2gpxvek382z4vjmwq534sghm6kp4ms423txlpwa5grc9kfjg0n4za4csjgdwe5ud4vy47zhm86h2zr5szyv9v38wd650gurf8u9hc9x9xh4m269wpxz9jmkfdpmnchltv2e52fmkfd04prrxj7una23ttsyq5m4zjck5cux8a6pem5ytrsky9z6y7am0mm867x5s3nxpfeued683e44z7wx84tx5kwzqfxtu3ttj99gygy9d5j5pwva5cdwcx8tffg9p7fh9ghmxqj3kpamwqytmu3j7vmrjxch4cgsksf4qpr3zwfx2nj6drpccjczwywz5j3492jrhvvjgpap4j39x4dtwk8qj42f7fd8ynjrsahs5h50pxtmxgzhl2nvzm525erpsf7aqgkp0ka6jd4c20jxfy8kr8q0q5auhefa8mqn44hxv0lfxd66pdzd5pvpn5v39juqtgy79j3g2444cd20c658up9ht6v8pvltadfydx5cx8c2awkptlgefwxkfy89p0x9x0pf854k3exdcmku4vtlz9yjne6zq2t6qjjtwpq36jcvkjt643urrek3h7h5kwwerjv48vrhvccswglt30v30vsvy5wxd6xg0n38eerfhdq83vr4624xlemf0yve3gd9c53lxrv27ysavc7yjle50sltcwmn2ap9vjx04g6c8r5u6mt95ldt2lqzjpr7d2qkrey8hxmvntefw7s36vhrr26e04tslxcrp74lmhh47u528yhz6zu9w7hzae6jzawv2nr3zwsrh3a2j87vsqty90wgrsle0w0f0u04afd3xx9k4mpqfuhgxu8kvsguz3cj
Click to see normal age key
age1u5w2rs8dkju0v4tvlyq92mcsp2cag4pr35mlaw3vw32qg50lfczs6yzu2r

Can't find the answer you're looking for? 📫 Get in touch with us.

Want to know more about backup options for your instance?


Ask about backups

Or see what's included in our hosting plans:

Plans and pricing