VPS Snaps

How to back up and restore a Discourse forum

Discourse backs itself up. Set backup_frequency under Admin, Backups, Settings and it writes a .tar.gz with the PostgreSQL database and the uploads on disk to /var/discourse/shared/standalone/backups/default, every 7 days and keeping 5 by default. The archive leaves out containers/app.yml, which holds your hostname, SMTP password, plugin list and any S3 keys, so copy that file separately. To restore, build Discourse at the same or a newer version with the same plugins, put the archive in the backups folder under its original name, and run discourse enable_restore and discourse restore <file> inside the container.

9 min readUpdated Checked against official documentation

What a Discourse backup contains

This guide assumes the standard Docker install from discourse_docker: everything lives in /var/discourse, the container is configured by containers/app.yml, and its data sits in shared/standalone, which the container sees as /shared. A backup, from the admin panel or the command line, is one archive:

DataIn the backup?Notes
PostgreSQL databaseYesdump.sql.gz, a pg_dump of the public schema: topics, posts, users, groups, categories, themes, and every setting changed in the admin panel
Uploads on the server's diskYes, by defaultImages, attachments and avatars. Turning backup_with_uploads off makes backups database-only.
Thumbnails and resized imagesNo, by defaultinclude_thumbnails_in_backups is off; Discourse regenerates them after a restore.
Uploads stored in S3NoSkipped unless you run discourse backup --s3-uploads; they stay in the bucket.
containers/app.ymlNoHostname, SMTP login, plugins, and settings set there as DISCOURSE_* environment variables
Plugin codeNoOnly plugin data in the database. Plugins are installed by the git clone lines in app.yml.
TLS certificates, Redis, logsNoLet's Encrypt issues new certificates, and a restore clears Redis anyway.

Archives are named after the site title, the time and the database migration version, for example example-forum-2026-10-04-033012-v20260925101500.tar.gz. A backup without uploads ends in .sql.gz. Discourse reads the version from the name when it restores, so never rename a backup.

Turn on scheduled backups

In the admin panel open Backups, then Settings, or search the site settings for backup. The settings that matter:

  • backup_frequency: days between automatic backups. The default is 7; set 1 for daily or 0 to stop them. The maximum is 30.
  • backup_time_of_day: when they run, in UTC. The default is 3:30.
  • maximum_backups: how many to keep, 5 by default. After each backup Discourse deletes the oldest beyond that number.
  • remove_older_backups: also delete backups older than this many days. Blank turns it off.
  • backup_with_uploads: on by default. Off makes scheduled backups database-only.
  • include_thumbnails_in_backups: off by default. On makes backups bigger but spares regenerating thumbnails after a restore.

Discourse builds the archive inside /var/discourse/shared/standalone/backups/default itself: a .tar grows there and is gzipped at the end. Anything that copies that folder must run after the backup has finished, not at the same minute. A scheduled backup that fails sends the admins a message in Discourse; one that succeeds sends nothing, so check the Backups page now and then.

Local backups sit on the same disk as the forum. If the server or its disk is lost, the backups go with it. Copy them somewhere else, or have Discourse upload them to S3 as shown below.

Make a backup from the command line

Before an upgrade or a move, take one by hand. Enter the container and run discourse backup:

Terminal
cd /var/discourse
Terminal
./launcher enter app
Terminal
discourse backup

It ends with Output file is in: and a path under /var/www/discourse/public/backups/default, the container's view of the same folder. --sql-only leaves uploads out; --s3-uploads downloads uploads stored in S3 into the archive. Type exit to leave the container. The launcher names the container after its config file, so from the host, or from cron, the same command runs without entering it:

Terminal
docker exec app discourse backup

Keep a copy of app.yml

/var/discourse/containers/app.yml is the other half of a backup. It names the forum's hostname, holds the SMTP password and any S3 keys, lists your plugins under hooks, and picks the templates the container is built from. Settings set there as DISCOURSE_* environment variables are not in the database at all. Without the file you can still restore, but you rebuild all of that by hand.

Copy it whenever it changes; with every backup is simplest. It holds credentials in plain text, so keep the copy private, and encrypt it if it lands next to the archives. To get both off the server, an rclone copy to a bucket from cron, scheduled a couple of hours after backup_time_of_day, is enough. Or let Discourse upload the archives itself.

Send backups to S3 instead

Discourse can upload each backup to an S3 bucket instead of keeping it on disk. In the admin panel, set s3_backup_bucket, fill in s3_access_key_id, s3_secret_access_key and s3_region, and set backup_location to S3. Discourse's guides recommend environment variables in app.yml instead. For backups only, add these to the env section:

/var/discourse/containers/app.yml
  DISCOURSE_S3_REGION: us-east-1
  DISCOURSE_S3_ACCESS_KEY_ID: <access-key-id>
  DISCOURSE_S3_SECRET_ACCESS_KEY: <secret-access-key>
  DISCOURSE_S3_BACKUP_BUCKET: <bucket-name>/discourse
  DISCOURSE_BACKUP_LOCATION: s3
Terminal
cd /var/discourse && ./launcher rebuild app
  • For an S3-compatible provider, add DISCOURSE_S3_ENDPOINT with the region's endpoint, without the bucket name in it. Discourse's provider list marks DigitalOcean Spaces, Linode, Vultr, Scaleway, Backblaze B2 and Cloudflare R2 as working; Linode and Vultr also need DISCOURSE_S3_HTTP_CONTINUE_TIMEOUT: 0.
  • Leave out DISCOURSE_USE_S3, DISCOURSE_S3_BUCKET and DISCOURSE_S3_CDN_URL: those move uploads to S3 as well, which also needs a CDN.
  • Use a private bucket, or a prefix such as <bucket-name>/discourse that holds nothing else. Backups and uploads in the same bucket and folder are no longer supported. s3_backup_bucket accepts only lowercase letters, digits, hyphens and slashes, so no dots.
  • From then on backups are not kept on the server; its disk only holds temporary files during a backup or restore. maximum_backups prunes the bucket too, unless s3_disable_cleanup is on.
  • Scaleway allows 1,000 parts per multipart upload, and Discourse's guide says that makes large backups fail there.

One bucket is still one copy. Turn on versioning so a mistake or a stolen key cannot delete every backup at once (setting up a backup bucket).

Restore onto a new server

Restore onto the same Discourse version or a newer one, with the same plugins. A restore runs the database migrations, so an older backup is brought up to date; a backup from a newer version is refused. Then:

  1. Point the domain at the new server first. With Let's Encrypt, the new server can only get a certificate once DNS answers with its address.
  2. Clone discourse_docker to /var/discourse, put your saved app.yml in containers/, and build (commands below). The official installer or ./discourse-setup also works, but then add your plugins to the new app.yml before restoring.
  3. Copy the archive into /var/discourse/shared/standalone/backups/default without renaming it.
  4. Enter the container, allow restores and restore by file name.
Terminal
git clone https://github.com/discourse/discourse_docker.git /var/discourse
Terminal
cp app.yml /var/discourse/containers/app.yml
Terminal
cd /var/discourse && ./launcher rebuild app
Terminal
mkdir -p /var/discourse/shared/standalone/backups/default
Terminal (on the machine holding the archive)
scp example-forum-2026-10-04-033012-v20260925101500.tar.gz root@<new-server>:/var/discourse/shared/standalone/backups/default/
Terminal
./launcher enter app
Terminal
discourse enable_restore
Terminal
discourse restore example-forum-2026-10-04-033012-v20260925101500.tar.gz

discourse restore with no file name lists the backups it can see. If backup_location is S3 but you copied the file to the server, add --location local; --location s3 restores straight from the bucket. Leave with exit. Discourse's guide suggests ./launcher rebuild app afterwards, a good moment to compare app.yml with the old server's.

The restore puts the site in read-only mode, pauses background jobs, clears Redis, moves the current tables into a schema named backup, loads the dump, migrates it and copies the uploads back. Posts with images are then rebaked in the background, so some images are missing for a while. The old tables are kept for 7 days: if the restored data is wrong, discourse rollback inside the container, or Rollback on the Backups page, puts the previous database back. Uploads are not rolled back.

A restore also sets disable_emails to non-staff, so a copy cannot email your users. On the real new server, set it back to no. If HTTPS is not working yet and you cannot sign in, Discourse's guide turns force_https off from the Rails console (rails c inside the container): SiteSetting.force_https = false.

Discourse's guides also describe moving a forum by stopping it and copying all of /var/discourse to the new server with rsync. That copies the database files themselves, so it only works with the app stopped. To test backups, restore one on a throwaway server now and then, with a hosts-file entry for the domain on your own computer; testing restores covers making it a routine.

Common errors

ErrorFix
You're trying to restore a more recent version of the schema. You should migrate first!The backup comes from a newer Discourse. Update the new server (./launcher rebuild app, or /admin/upgrade) and restore again.
Restores are not allowed.Run discourse enable_restore in the container, or turn on allow_restore in the Backups settings.
Migration version is missing from the filename.The archive was renamed. Give it back its original name, which ends in -v and 14 digits.
The backup filename contains invalid characters. Valid characters are a-z 0-9 . - _.Usually a browser added (1) to the name when downloading. Rename the file to the original name.
The backup file should be a .tar.gz archive.Upload the .tar.gz or .sql.gz exactly as Discourse wrote it, not a zipped or repacked copy.
There is not enough space on disk to upload this backup.Free space on the disk holding shared/standalone. A restore also needs room to unpack the archive.
ERROR: The uncompressed file is too big. Consider increasing the hidden "decompressed_backup_max_file_size_mb" setting.The limit defaults to 100000 MB. In rails c inside the container, raise it, for example SiteSetting.decompressed_backup_max_file_size_mb = 200000, and restore again.
An operation is currently running. Can't start a new job right now.A backup or restore is running. Wait, or cancel it on the Backups page. The flag expires about a minute after the process that set it stops.
No emails after a restoreThe restore set disable_emails to non-staff. Set it back to no.

Frequently asked questions

Where does Discourse store its backups?
On a standard Docker install, in /var/discourse/shared/standalone/backups/default on the host. When backup_location is S3, they go to the bucket in s3_backup_bucket instead.
Does a Discourse backup include uploads and images?
Uploads on the server's disk are included by default. Uploads stored in S3 are not, unless you run discourse backup --s3-uploads, and thumbnails are left out unless include_thumbnails_in_backups is on.
How do I restore a Discourse backup from the command line?
Put the archive in the backups folder without renaming it, run ./launcher enter app, then discourse enable_restore and discourse restore followed by the file name.
Can I restore a Discourse backup on an older version?
No. The restore refuses a backup with a newer database schema. Update the destination first; an older backup is migrated forward during the restore.
Is app.yml included in a Discourse backup?
No. Copy /var/discourse/containers/app.yml separately; it holds the hostname, SMTP credentials, plugin list and any S3 settings.

How this was checked

Commands, limits and prices were checked against these official pages, on October 4, 2026: