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.
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:
| Part | Where | What it holds |
|---|---|---|
| PostgreSQL database | mattermost, as named in SqlSettings.DataSource | Messages, channels, teams, users, plugin key-value data, and the configuration too if you moved it into the database |
config/ | /opt/mattermost/config | config.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/mattermost | Unpacked plugin code, which the upgrade guide also preserves |
| Service unit, TLS files | /etc/systemd/system/mattermost.service, certificates kept in /opt/mattermost | Not data, but you need them to start the server the same way |
logs/ | /opt/mattermost/logs | Not 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:
runuser -u postgres -- pg_dump -Fc mattermost > /var/backups/mattermost/mattermost.dumprunuser -u postgresruns pg_dump as thepostgressystem user, which logs in over the local socket without a password. Root's shell does the redirect, so the file belongs to root.-Fcwrites PostgreSQL's custom format: compressed, restored withpg_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 examplepg_dump -h db.internal -U mmuser -Fc mattermostwith 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/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" -deletechmod 700 /usr/local/bin/mattermost-backup.sh15 3 * * * root /usr/local/bin/mattermost-backup.sh >> /var/log/mattermost-backup.log 2>&1cd /starts from a folder thepostgresandmattermostusers can enter.- The version file records the exact version and edition (
Build Enterprise Ready) a restore needs.mattermost versiondoes not touch the database. - Each archive is written as
.partialand renamed when complete; thetrapdeletes 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/exportskips 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:
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-channelsand--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:
sudo -u mattermost /opt/mattermost/bin/mmctl --local export createsudo -u mattermost /opt/mattermost/bin/mmctl --local export listmmctl 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:
sudo -u postgres psqlCREATE 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:
runuser -u postgres -- pg_restore -d mattermost < /var/backups/mattermost/mattermost-db-2026-10-04_0315.dumpDownload 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:
wget https://releases.mattermost.com/<version>/mattermost-<version>-linux-amd64.tar.gztar -xzf mattermost-<version>-linux-amd64.tar.gz -C /optuseradd --system --user-group mattermostUnpack config, data and the plugin folders over it and give everything to the mattermost user:
tar -xzf /var/backups/mattermost/mattermost-files-2026-10-04_0315.tar.gz -C /opt/mattermostchown -R mattermost:mattermost /opt/mattermostIf 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:
curl -s http://localhost:8065/api/v4/system/pingThe 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:
docker compose exec -T postgres pg_restore -U mmuser -d mattermost < /var/backups/mattermost/mattermost.dumpTesting restores covers repeating this on a throwaway server on a schedule.
Common errors
| Error | Fix |
|---|---|
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 header | The 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 mismatch | The 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 public | On 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 mode | Set ServiceSettings.EnableLocalMode to true, restart Mattermost, and run mmctl as the same user as the server. |
| Public file links fail after a restore | The 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 missing | data 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:
- Mattermost docs: Backup and disaster recovery
- Mattermost docs: Bulk export tool
- Mattermost docs: Bulk loading data (password field)
- Mattermost docs: mmctl command line tool (export, local mode)
- Mattermost docs: Command line tools (mattermost version)
- Mattermost docs: Store configuration in your database
- Mattermost docs: Experimental configuration settings (export directory and retention, local mode, plugin directories)
- Mattermost docs: Site configuration settings (public link salt)
- Mattermost docs: Environment configuration settings (file storage)
- Mattermost docs: Manage plugins
- Mattermost docs: Software and hardware requirements
- Mattermost docs: Important upgrade notes (v11.0 MySQL, v10.11 upgrade path)
- Mattermost docs: Migration guidelines from MySQL to PostgreSQL
- Mattermost docs: Server preparations (database preparation)
- Mattermost docs: Deploy Mattermost on Linux (tarball)
- Mattermost docs: Deploy Mattermost using containers (Docker)
- Mattermost docs: Docker deployment troubleshooting (version tags)
- Mattermost docs: Upgrade Mattermost Server
- Mattermost docs: Upgrade PostgreSQL (pg_dump -Fc, Docker)
- Mattermost docs: PostgreSQL installation troubleshooting
- mattermost/docker: env.example and docker-compose.yml
- Mattermost v11.11.1 source: mmctl export commands
- Mattermost v11.11.1 source: bulk export (app/export.go)
- Mattermost v11.11.1 source: system ping endpoint (api4/system.go)
- PostgreSQL documentation: pg_dump
- PostgreSQL documentation: pg_restore
- PostgreSQL documentation: Predefined roles (pg_read_all_data)
- PostgreSQL 17 source: pg_backup_db.c (server version mismatch)
- PostgreSQL 17 source: pg_backup_archiver.h (archive format versions)
- Docker documentation: docker compose exec