VPS Snaps

How to back up and restore Mattermost

Mattermost has no backup command. Its documentation says to back up three things separately: the PostgreSQL database (pg_dump -Fc), the config folder with config.json, and the file store, /opt/mattermost/data unless your files are in S3. mmctl export create is a migration export, not a backup. To restore, install the same Mattermost version, restore the database before Mattermost first starts, put config and data back, start it, and only then upgrade.

10 min readUpdated Checked against official documentation

What to back up

This guide assumes the tarball install in /opt/mattermost with PostgreSQL on the same machine and commands run as root; Docker follows below. Mattermost's backup guide lists the parts, which have to be backed up and restored separately:

PartWhereWhat it holds
PostgreSQL databasemattermost, as named in SqlSettings.DataSourceMessages, channels, teams, users, plugin key-value data, and the configuration too if you moved it into the database
config//opt/mattermost/configconfig.json with the database password, SMTP and S3 keys and the public link salt; SAML certificates; mattermost.environment when the configuration lives in the database
File store/opt/mattermost/data (FileSettings.Directory)Attachments, profile pictures, custom emoji, plugins installed from the Marketplace or System Console, export files
plugins/, client/plugins//opt/mattermostUnpacked plugin code, which the upgrade guide also preserves
Service unit, TLS files/etc/systemd/system/mattermost.service, certificates kept in /opt/mattermostNot data, but you need them to start the server the same way
logs//opt/mattermost/logsNot needed for a restore

The configuration is not optional. A fresh config.json has a new public link salt, which invalidates every public file link users have shared, and it loses everything set in the System Console.

Mattermost's v11.0 upgrade notes say support for MySQL has ended, and its requirements list PostgreSQL 14 or later. A server still on MySQL runs v10 or older: back it up with mysqldump, and move it with Mattermost's MySQL-to-PostgreSQL migration guide and its migration-assist tool before upgrading. MariaDB 10 and later is not supported at all.

If your files are in S3 (FileSettings.DriverName set to amazons3), Mattermost's guide says you can typically keep them where they are without a backup. That covers losing the server, not a deleted bucket or file: turn on bucket versioning (how) or copy the bucket to another provider.

Dump the database

Mattermost says to back up the database using standard procedures, which for PostgreSQL means pg_dump; its own PostgreSQL upgrade guide uses the custom format:

Terminal
runuser -u postgres -- pg_dump -Fc mattermost > /var/backups/mattermost/mattermost.dump
  • runuser -u postgres runs pg_dump as the postgres system user, which logs in over the local socket without a password. Root's shell does the redirect, so the file belongs to root.
  • -Fc writes PostgreSQL's custom format: compressed, restored with pg_restore, which can also restore single tables.
  • pg_dump reads one consistent snapshot while Mattermost keeps running, so the dump itself needs no downtime.
  • For a database elsewhere, use the host, user and database from SqlSettings.DataSource, for example pg_dump -h db.internal -U mmuser -Fc mattermost with the password in ~/.pgpass. pg_dump must be the server's major version or newer.

Backing up PostgreSQL with pg_dump covers the formats and flags in more depth.

A nightly backup script

Mattermost's guide adds that a clean backup needs Mattermost stopped for the whole backup, "otherwise the database and files may become out of sync". If a few minutes' outage at night is fine, stop it. If not, dump the database first and copy the files after, as this script does: every file the dump refers to is already on disk when the copy starts, unless someone deletes it in those minutes, and files uploaded in between are only extra.

/usr/local/bin/mattermost-backup.sh
#!/usr/bin/env bash
set -euo pipefail
umask 077
cd /

MM_DIR="/opt/mattermost"
BACKUP_DIR="/var/backups/mattermost"
KEEP_DAYS=7
STAMP="$(date +%Y-%m-%d_%H%M)"
DB_OUT="$BACKUP_DIR/mattermost-db-$STAMP.dump"
FILES_OUT="$BACKUP_DIR/mattermost-files-$STAMP.tar.gz"

mkdir -p "$BACKUP_DIR"
trap 'rm -f "$DB_OUT.partial" "$FILES_OUT.partial"' EXIT

runuser -u mattermost -- "$MM_DIR/bin/mattermost" version > "$BACKUP_DIR/mattermost-version-$STAMP.txt"

runuser -u postgres -- pg_dump -Fc mattermost > "$DB_OUT.partial"
mv "$DB_OUT.partial" "$DB_OUT"

rc=0
tar -czf "$FILES_OUT.partial" --exclude=data/export -C "$MM_DIR" \
  config data plugins client/plugins || rc=$?
if [ "$rc" -gt 1 ]; then
  exit "$rc"
fi
mv "$FILES_OUT.partial" "$FILES_OUT"

find "$BACKUP_DIR" -maxdepth 1 -name 'mattermost-*' -type f -mtime +"$KEEP_DAYS" -delete
Terminal
chmod 700 /usr/local/bin/mattermost-backup.sh
/etc/cron.d/mattermost-backup
15 3 * * * root /usr/local/bin/mattermost-backup.sh >> /var/log/mattermost-backup.log 2>&1
  • cd / starts from a folder the postgres and mattermost users can enter.
  • The version file records the exact version and edition (Build Enterprise Ready) a restore needs. mattermost version does not touch the database.
  • Each archive is written as .partial and renamed when complete; the trap deletes a half-written one.
  • tar exits with 1 when a file changed while it was read, which happens on a live server; anything higher stops the script. --exclude=data/export skips bulk export files, which can be large.

For the clean backup Mattermost describes, add systemctl stop mattermost before the dump and systemctl start mattermost after the tar; users are disconnected for those minutes. Copy the service unit once, and copy the archives off the machine. A large data folder makes a full tar every night slow; restic or rsync copies only what changed.

Docker installs

Mattermost's Docker setup, cloned from github.com/mattermost/docker, keeps its data in folders next to docker-compose.yml: ./volumes/app/mattermost/config, data, plugins and client/plugins for Mattermost, and ./volumes/db/var/lib/postgresql/data for PostgreSQL. The .env file holds the database password and the image tags, so back it up too. Dump from inside the database container, from the folder with docker-compose.yml, so pg_dump matches the server, postgres:18-alpine by default:

Terminal
docker compose exec -T postgres pg_dump -U mmuser -Fc mattermost > /var/backups/mattermost/mattermost.dump

-T turns off the pseudo-terminal that docker compose exec allocates by default, which a binary dump and a cron job both need. mmuser and mattermost are POSTGRES_USER and POSTGRES_DB from .env. Do not copy ./volumes/db while the container runs; use the dump. The version a restore needs is MATTERMOST_IMAGE_TAG in .env, and Mattermost recommends a specific version tag in production rather than a generic release-x one.

What mmctl export covers, and what it does not

mmctl export create starts a bulk export job. The result is a zip with a JSONL file of teams, channels, users, posts and threads, reactions, preferences, custom emoji, roles, permission schemes and bots, plus file attachments unless you add --no-attachments. It is built to move content into another Mattermost server with the bulk import tool. It is not a backup:

  • No configuration, no plugins and no plugin data. Mattermost's docs list webhooks and deleted objects as not yet supported.
  • No passwords. When an imported user signs in with a password and the file has none, the bulk loader generates one, so everyone has to reset theirs.
  • Archived channels and profile pictures are left out unless you add --include-archived-channels and --include-profile-pictures.

On the server, run it as the mattermost user in local mode, which needs ServiceSettings.EnableLocalMode set to true in config.json:

Terminal
sudo -u mattermost /opt/mattermost/bin/mmctl --local export create
Terminal
sudo -u mattermost /opt/mattermost/bin/mmctl --local export list

mmctl export job show <job-id> reports progress, and mmctl export download <name> fetches the file through the API; by default it also sits in data/export on the server. Exports are deleted after 30 days (ExportSettings.RetentionDays).

Restore onto a new server

Restore onto the same version and edition first, check it, then upgrade. Do not start Mattermost against the empty database before the restore, or it creates its own tables first. Install PostgreSQL 14 or later, the source's major version or newer, and create the role and database as Mattermost's database preparation guide does, with the password from your saved config.json:

Terminal
sudo -u postgres psql
psql
CREATE DATABASE mattermost WITH ENCODING 'UTF8' LC_COLLATE='en_US.UTF-8' LC_CTYPE='en_US.UTF-8' TEMPLATE=template0;
CREATE USER mmuser WITH PASSWORD '<password>';
GRANT ALL PRIVILEGES ON DATABASE mattermost to mmuser;
ALTER DATABASE mattermost OWNER TO mmuser;
\c mattermost
ALTER SCHEMA public OWNER TO mmuser;
GRANT USAGE, CREATE ON SCHEMA public TO mmuser;

Quit psql with \q and restore the dump, still as postgres, which recreates every table owned by mmuser:

Terminal
runuser -u postgres -- pg_restore -d mattermost < /var/backups/mattermost/mattermost-db-2026-10-04_0315.dump

Download the version in your version file and unpack it to /opt. For Team Edition the file is mattermost-team-<version>-linux-amd64.tar.gz; use arm64 on ARM servers. Then create the user as the install guide does:

Terminal
wget https://releases.mattermost.com/<version>/mattermost-<version>-linux-amd64.tar.gz
Terminal
tar -xzf mattermost-<version>-linux-amd64.tar.gz -C /opt
Terminal
useradd --system --user-group mattermost

Unpack config, data and the plugin folders over it and give everything to the mattermost user:

Terminal
tar -xzf /var/backups/mattermost/mattermost-files-2026-10-04_0315.tar.gz -C /opt/mattermost
Terminal
chown -R mattermost:mattermost /opt/mattermost

If the database host or password changed, edit SqlSettings.DataSource in config.json; if the address changed, ServiceSettings.SiteURL. Put the systemd unit back, run systemctl daemon-reload and systemctl start mattermost, and check:

Terminal
curl -s http://localhost:8065/api/v4/system/ping

The reply includes "status":"OK". Then sign in and open an old message with an attachment: the message comes from the database, the file from data. Only then upgrade, following Mattermost's upgrade guide and its important upgrade notes for your path. For example, they warn against upgrading from 10.11.17 or later to 11.7.2 or earlier, because of a bug with database migration numbers that 11.7.3 fixes.

On Docker: clone the repository, put back .env and ./volumes/app/mattermost (owned by 2000:2000, as the install guide sets), and start only the database with docker compose up -d postgres. It creates the empty mattermost database from .env. Restore into it, then start everything with your usual docker compose command:

Terminal
docker compose exec -T postgres pg_restore -U mmuser -d mattermost < /var/backups/mattermost/mattermost.dump

Testing restores covers repeating this on a throwaway server on a schedule.

Common errors

ErrorFix
pg_restore: error: input file appears to be a text format dump. Please use psql.The dump is plain SQL, not -Fc. Load it with psql -d mattermost -f <file>, or gunzip -c <file>.sql.gz | psql -d mattermost if it is gzipped.
pg_restore: error: unsupported version (1.16) in file headerThe dump was made by pg_dump 17 or 18 and this pg_restore is older. Restore with the same or a newer PostgreSQL client, for example inside the database container.
pg_dump: error: aborting because of server version mismatchThe host's pg_dump is older than the server, common next to the postgres:18-alpine container. Run pg_dump inside the container with docker compose exec.
Peer authentication failed for user "mmuser"Over the local socket, PostgreSQL matches the Linux user name. Add -h localhost to log in with the password, or run the command as postgres.
permission denied for schema publicOn PostgreSQL 15 and later, mmuser must own the database and the public schema: run the ALTER DATABASE, ALTER SCHEMA and GRANT lines above.
invalid LC_COLLATE locale name: "en_US.UTF-8"Generate the locale with locale-gen en_US.UTF-8, as Mattermost's guide says, then create the database again.
socket file "/var/tmp/mattermost_local.socket" doesn't exists, please check the server configuration for local modeSet ServiceSettings.EnableLocalMode to true, restart Mattermost, and run mmctl as the same user as the server.
Public file links fail after a restoreThe server has a new config.json, so a new public link salt. Restore the original config folder.
Messages are back but attachments and avatars are missingdata was not restored, is not where FileSettings.Directory points, or is not owned by mattermost.

Frequently asked questions

How do I back up a Mattermost server?
Dump the PostgreSQL database with pg_dump -Fc and copy the config, data, plugins and client/plugins folders. Mattermost has no single backup command.
Is mmctl export a full Mattermost backup?
No. It exports teams, channels, users, posts and attachments for moving to another server, but not the configuration, plugins, webhooks, deleted items or user passwords.
Does Mattermost still support MySQL?
No. Support for MySQL ended in v11.0, and current requirements list PostgreSQL 14 or later. A server on MySQL has to migrate to PostgreSQL before upgrading to v11.
Can I restore a Mattermost backup on a newer version?
Restore onto the same version first and check it, then upgrade. Starting a newer version on the restored database is an upgrade, with the same rules about upgrade paths.
Do I need to back up Mattermost files stored in S3?
Mattermost's guide says you can typically leave them in the bucket. Turn on versioning or copy the bucket elsewhere if a deleted bucket or file must be recoverable.

How this was checked

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