How to back up and restore a Mastodon server
Back up four things, in the order Mastodon's documentation ranks them: the PostgreSQL database (pg_dump -Fc), the secrets in .env.production, uploaded files in public/system unless they live in object storage, and, optionally, Redis. To restore, follow Mastodon's migration guide: create the database from template0, load it with pg_restore, put the files and .env.production back, start Mastodon, then rebuild home feeds with tootctl feeds build.
What to back up
Paths here are for a source install made with the Production Guide, in /home/mastodon/live. With Docker Compose, .env.production and public/system sit next to docker-compose.yml.
| What | Where | If you lose it |
|---|---|---|
| PostgreSQL database | mastodon_production | Everything: accounts, posts, follows. The server is gone. |
| Secrets | .env.production | Everyone is logged out; two-factor login and Web Push stop working. |
| Uploaded files | public/system | Avatars, headers and media attachments. Mastodon keeps running. |
| Redis | /var/lib/redis/dump.rdb | Queued Sidekiq jobs and their retries. Feeds can be rebuilt. |
| Elasticsearch, if used | Its data folder | Nothing: tootctl search deploy rebuilds the indexes. |
| nginx, systemd units, certificates | /etc/nginx/sites-available/mastodon, /etc/systemd/system/mastodon-*.service | Convenience only. They can be recreated. |
Mastodon's backup page considers hardware failure only. A deleted account, a bad moderation action or a broken upgrade is a human or software error, and the fix for those is history: dumps from several past days, not just last night's.
The secrets in .env.production
The docs call the secrets the easiest part, since they never change. They also can't be recreated. As of Mastodon 4.7 they are:
SECRET_KEY_BASEsigns browser sessions. A new value logs everyone out.ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY,ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEYandACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALTencrypt data inside the database, including two-factor secrets since 4.3. Mastodon refuses to start without them, and its sample config warns that changing them causes data loss. A database backup without these three is incomplete.VAPID_PRIVATE_KEYandVAPID_PUBLIC_KEYsign Web Push notifications. New keys break existing push subscriptions.OTP_SECRETexists only on servers set up before 4.4.0, which removed it. The configuration page still lists it; keep it if your file has it.
The file also holds the database, SMTP and object storage passwords, and LOCAL_DOMAIN, which can't be changed safely once set. Store it only where the backups are encrypted.
Dump PostgreSQL in custom format
Run pg_dump as the mastodon user, as the migration guide does. It reads a consistent snapshot while Mastodon keeps running:
sudo -u mastodon pg_dump -Fc mastodon_production -f /home/mastodon/mastodon.dump-Fcwrites the custom format: compressed, read bypg_restore, which can load it with parallel jobs or restore single tables.- The Production Guide creates the
mastodonrole without a password; it connects over the local socket as the matching system user. - Big databases take time: the migration guide warns of hours for a 15 GB dump.
With the repository's docker-compose.yml, PostgreSQL runs in the db service, and mastodon:setup defaults to user postgres and database postgres there; check DB_USER and DB_NAME in .env.production. Add -T: docker compose exec allocates a terminal by default, and a binary dump should not pass through one.
docker compose exec -T db pg_dump -U postgres -Fc postgres > mastodon.dumpA Docker restore follows the same steps, into an empty database, with the file fed in on standard input. Parallel -j needs a file path, so it is left out:
docker compose exec -T db pg_restore -U postgres --no-owner -d postgres < mastodon.dumpUploaded files: originals and the remote cache
With local storage, uploads live in public/system, or in PAPERCLIP_ROOT_PATH if you set it. Mastodon also stores copies of media from other servers, and since version 3.1.4 they go in their own cache/ folder:
| Folder | Holds | Back up? |
|---|---|---|
public/system/cache | Copies of other servers' media attachments, avatars, headers and emoji, plus all link preview images | No |
Everything else in public/system | Your users' media, avatars and headers, your custom emoji and site images, and data exports | Yes |
The cache is often the largest part, and Mastodon fetches it again when needed. RAILS_ENV=production bin/tootctl media usage, run as mastodon in ~/live, shows the total and local size of each kind of file. Servers installed before 3.1.4 may still keep remote files mixed in with local ones until tootctl upgrade storage-schema moves them.
With object storage (S3_ENABLED=true), Mastodon's docs say you don't need to back up the bucket, since the provider handles hardware failure. That doesn't cover deletion, a lost account or leaked keys. To be safe, copy the bucket into another provider's account with rclone.
Redis and Elasticsearch
The docs call losing Redis almost harmless: you lose queued Sidekiq jobs and scheduled retries, and home and list feeds are rebuilt with tootctl. Copying /var/lib/redis/dump.rdb is enough if you want it; the Redis guide explains why that copy is safe while Redis runs. Elasticsearch needs no backup: tootctl search deploy rebuilds it from the database.
Automate it
This script uses restic, set up as in that guide with /etc/restic/env, so every copy is encrypted, versioned and off the server. It runs as root:
#!/usr/bin/env bash
set -euo pipefail
PATH=/usr/local/bin:/usr/bin:/bin
cd /
set -a; . /etc/restic/env; set +a
LIVE=/home/mastodon/live
VERSION=$(sudo -u mastodon git -C "$LIVE" describe --tags)
restic backup --tag mastodon-db --tag "$VERSION" --stdin-filename mastodon.dump \
--stdin-from-command -- \
sudo -u mastodon pg_dump -Fc -Z 0 mastodon_production
restic backup --tag mastodon-files \
--exclude "$LIVE/public/system/cache" \
"$LIVE/.env.production" "$LIVE/public/system" /var/lib/redis/dump.rdb \
/etc/nginx/sites-available/mastodon /etc/systemd/system/mastodon-*.service
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prunechmod 700 /usr/local/bin/mastodon-backup.sh15 4 * * * root /usr/local/bin/mastodon-backup.sh >> /var/log/mastodon-backup.log 2>&1cd /starts in a folder themastodonuser can read, since its commands inherit root's working directory.PATHadds/usr/local/bin, where the restic guide installs restic.git describe --tagsprints the release the source checkout is on, such asv4.7.3. It becomes a tag on the database snapshot, so you know which version to restore onto.-Z 0turns off pg_dump's compression. The file stays in custom format, restic can deduplicate unchanged data between nights, and it compresses the repository itself. If pg_dump fails, restic saves no snapshot.- Delete any path your server doesn't have, such as
dump.rdbwhen Redis persistence is off; restic reports missing paths as errors. forgetgroups snapshots by host and paths, so the database and the files each keep 7 daily, 4 weekly and 6 monthly copies.
Restore onto a new server
These are the steps of Mastodon's migration guide, fed from the backup. Set up the new machine with the Production Guide but don't run mastodon:setup, and in the checkout step use the version the backup came from. Install restic, put back /etc/restic/env from the copy you keep off the server, and list the dumps with their version tags:
set -a; . /etc/restic/env; set +arestic snapshots --tag mastodon-dbLoad the database into a new, empty one:
restic dump --tag mastodon-db latest mastodon.dump > /home/mastodon/mastodon.dumpchown mastodon:mastodon /home/mastodon/mastodon.dumpsudo -u mastodon createdb -T template0 mastodon_productionsudo -u mastodon pg_restore -Fc -j4 -U mastodon -n public --no-owner --role=mastodon -d mastodon_production /home/mastodon/mastodon.dump-T template0copies PostgreSQL's untouched template, so nothing added to the default template can clash with the dump.-j4loads data and builds indexes with four parallel jobs; use your CPU count.-n publicrestores only thepublicschema, where Mastodon's tables live.--no-owner --role=mastodonmakesmastodonown everything. If the new server's role has another name, change-Uand--role.
Stop Redis before its file comes back, or it overwrites the restored copy, then restore the files. Owners are restored by numeric ID, which may belong to another user on the new machine, so reset them:
systemctl stop redis-serverrestic restore latest --tag mastodon-files --target /chown -R mastodon:mastodon /home/mastodon/live/public/system /home/mastodon/live/.env.productionchown redis:redis /var/lib/redis/dump.rdbAs the mastodon user (su - mastodon), in ~/live, compile the assets:
RAILS_ENV=production bundle exec rails assets:precompileAs root, start everything:
systemctl daemon-reloadsystemctl start redis-serversystemctl enable --now mastodon-web mastodon-sidekiq mastodon-streamingsystemctl restart nginxBack as mastodon in ~/live, rebuild home and list feeds. With no username, it does every active user and prints how many it regenerated; this can take a long time on a big server. If you use Elasticsearch, run RAILS_ENV=production bin/tootctl search deploy too.
RAILS_ENV=production bin/tootctl feeds buildRemote media is missing because cache/ was left out. To fetch the last week's again, use --force: the database still names those files, and without it tootctl skips them. bin/tootctl accounts refresh --all refetches avatars and headers for every remote account the server knows, which is a lot of requests on a big server.
RAILS_ENV=production bin/tootctl media refresh --days 7 --forceFinally, point DNS at the new server and set up nginx and certificates as the migration guide describes. Moving to a new provider? See migrating a server.
Test the backup without a second live server
Don't start a full restored copy with network access. It carries your domain, your SMTP login and every local account's signing key, which the accounts table stores. With Sidekiq running, it can deliver posts to other servers and send email as your server.
Test the parts instead, on any machine with PostgreSQL of the same major version or newer:
install -d -o postgres -m 700 /srv/restore-testrestic dump --tag mastodon-db latest mastodon.dump > /srv/restore-test/mastodon.dumpsudo -u postgres createdb -T template0 mastodon_restore_testsudo -u postgres pg_restore -j4 --no-owner -d mastodon_restore_test /srv/restore-test/mastodon.dumpsudo -u postgres psql -d mastodon_restore_test -c "SELECT (SELECT count(*) FROM users) AS users, (SELECT count(*) FROM statuses WHERE local OR uri IS NULL) AS local_posts, (SELECT max(created_at) FROM statuses WHERE local OR uri IS NULL) AS newest_local_post, (SELECT count(*) FROM media_attachments WHERE remote_url = '' AND file_file_name IS NOT NULL) AS local_media;"Run the same query on the live server (sudo -u mastodon psql mastodon_production). The counts should match as of the dump, and newest_local_post shows its age. Then restore the uploads and count the original files:
restic restore latest --tag mastodon-files --target /srv/restore-test --include /home/mastodon/live/public/systemfind /srv/restore-test/home/mastodon/live/public/system/media_attachments/files -path '*/original/*' -type f | wc -lThat number should be close to local_media; a big gap means missing files. Clean up:
sudo -u postgres dropdb mastodon_restore_testrm -rf /srv/restore-testCommon errors
| Error | Fix |
|---|---|
Mastodon now requires that these variables are set at startup | The three ACTIVE_RECORD_ENCRYPTION_* keys are missing. Restore .env.production from the same backup as the database. |
| Everyone is logged out | SECRET_KEY_BASE differs from the old server's. |
| Push notifications stopped | The VAPID keys changed. Restore the originals. |
role "..." does not exist from pg_restore | Restore with --no-owner --role=mastodon, as above. |
database "mastodon_production" already exists | Setup or an earlier attempt made it. Drop it with dropdb if it holds nothing you need, then restore. |
| Empty home timelines | Run tootctl feeds build. |
Frequently asked questions
- What do I need to back up on a Mastodon server?
- The PostgreSQL database, .env.production, and public/system if media is stored locally. Redis is optional, and Elasticsearch can be rebuilt.
- Do I need to back up public/system/cache?
- No. It holds copies of media from other servers. After a restore, tootctl media refresh with --days and --force fetches recent ones again.
- Do I need to back up Mastodon media stored in S3?
- Mastodon's docs leave hardware failure to the provider. A copy in another account still protects you from deletion, a lost account or leaked keys.
- What happens if I lose Mastodon's Redis data?
- Very little: queued Sidekiq jobs and retries are lost, and feeds are rebuilt with tootctl feeds build.
How this was checked
Commands, limits and prices were checked against these official pages, on October 4, 2026:
- Mastodon documentation: Backing up your server
- Mastodon documentation: Migrating to a new machine
- Mastodon documentation: Using the admin CLI (tootctl)
- Mastodon documentation: Configuring your environment
- Mastodon documentation: Installing from source
- Mastodon documentation: Configuring object storage
- Mastodon v4.3.0 release notes (Active Record encryption secrets)
- Mastodon v4.4.0 release notes (OTP_SECRET removed)
- Mastodon v4.7.3: .env.production.sample
- Mastodon v4.7.3: config/initializers/active_record_encryption.rb
- Mastodon v4.7.3: config/initializers/paperclip.rb (cache prefix)
- Mastodon v4.7.3: tootctl feeds
- Mastodon v4.7.3: tootctl media
- Mastodon v4.7.3: mastodon:setup (Docker defaults)
- Mastodon v4.7.3: docker-compose.yml
- Mastodon v4.7.3: db/schema.rb
- PostgreSQL documentation: pg_dump
- PostgreSQL documentation: pg_restore
- PostgreSQL documentation: createdb
- Docker documentation: docker compose exec
- restic documentation: Backing up
- restic documentation: Restoring from backup
- restic documentation: Removing backup snapshots