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.
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:
| Data | In the backup? | Notes |
|---|---|---|
| PostgreSQL database | Yes | dump.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 disk | Yes, by default | Images, attachments and avatars. Turning backup_with_uploads off makes backups database-only. |
| Thumbnails and resized images | No, by default | include_thumbnails_in_backups is off; Discourse regenerates them after a restore. |
| Uploads stored in S3 | No | Skipped unless you run discourse backup --s3-uploads; they stay in the bucket. |
containers/app.yml | No | Hostname, SMTP login, plugins, and settings set there as DISCOURSE_* environment variables |
| Plugin code | No | Only plugin data in the database. Plugins are installed by the git clone lines in app.yml. |
| TLS certificates, Redis, logs | No | Let'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 is3: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:
cd /var/discourse./launcher enter appdiscourse backupIt 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:
docker exec app discourse backupKeep 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:
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: s3cd /var/discourse && ./launcher rebuild app- For an S3-compatible provider, add
DISCOURSE_S3_ENDPOINTwith 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 needDISCOURSE_S3_HTTP_CONTINUE_TIMEOUT: 0. - Leave out
DISCOURSE_USE_S3,DISCOURSE_S3_BUCKETandDISCOURSE_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>/discoursethat holds nothing else. Backups and uploads in the same bucket and folder are no longer supported.s3_backup_bucketaccepts 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_backupsprunes the bucket too, unlesss3_disable_cleanupis 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:
- 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.
- Clone discourse_docker to
/var/discourse, put your saved app.yml incontainers/, and build (commands below). The official installer or./discourse-setupalso works, but then add your plugins to the new app.yml before restoring. - Copy the archive into
/var/discourse/shared/standalone/backups/defaultwithout renaming it. - Enter the container, allow restores and restore by file name.
git clone https://github.com/discourse/discourse_docker.git /var/discoursecp app.yml /var/discourse/containers/app.ymlcd /var/discourse && ./launcher rebuild appmkdir -p /var/discourse/shared/standalone/backups/defaultscp example-forum-2026-10-04-033012-v20260925101500.tar.gz root@<new-server>:/var/discourse/shared/standalone/backups/default/./launcher enter appdiscourse enable_restorediscourse restore example-forum-2026-10-04-033012-v20260925101500.tar.gzdiscourse 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
| Error | Fix |
|---|---|
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 restore | The 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:
- Discourse Meta: Configure automatic backups for Discourse
- Discourse Meta: Restore a backup from the command line
- Discourse Meta: Move your Discourse instance to a different server
- Discourse Meta: Move a Discourse site to another VPS with rsync
- Discourse Meta: Configure an S3 compatible object storage provider for uploads
- Discourse Meta: Set up file and image uploads to S3
- Discourse: Install Discourse on a cloud server (INSTALL-cloud.md)
- discourse_docker: samples/standalone.yml
- discourse_docker: templates/web.template.yml (shared folders, discourse wrapper)
- discourse_docker: launcher (container name, enter)
- Discourse v2026.9.0: config/site_settings.yml (backup settings)
- Discourse v2026.9.0: config/locales/server.en.yml (setting descriptions, backup errors)
- Discourse v2026.9.0: script/discourse (backup, restore, enable_restore, rollback)
- Discourse v2026.9.0: lib/backup_restore/creator.rb
- Discourse v2026.9.0: lib/backup_restore/restorer.rb
- Discourse v2026.9.0: lib/backup_restore/database_restorer.rb
- Discourse v2026.9.0: lib/backup_restore/meta_data_handler.rb
- Discourse v2026.9.0: lib/backup_restore.rb (running flag)
- Discourse v2026.9.0: app/controllers/admin/backups_controller.rb