VPS Snaps

How to back up and restore Uptime Kuma

Uptime Kuma keeps everything in one data folder, /app/data in Docker: the kuma.db SQLite database (or a MariaDB database), status page logos and db-config.json. Back it up by stopping the container and archiving the folder, or, on SQLite, by copying kuma.db live with sqlite3 .backup and archiving the rest. Version 2 removed the JSON export, so the data folder is the only supported backup.

8 min readUpdated Checked against official documentation

What Uptime Kuma keeps, and where

Uptime Kuma is an open-source (MIT-licensed), self-hosted monitoring tool created by Louis Lam. Version 2.0.0, released in October 2025, added MariaDB alongside SQLite. This guide follows 2.5.5, the current release (September 2026).

The official image keeps its data in /app/data. The README's docker run command mounts a named volume called uptime-kuma; the project's compose.yaml mounts a ./data folder next to the compose file. Outside Docker it's ./data in the install folder, or wherever DATA_DIR points. Check what your container uses:

Terminal
docker inspect --format '{{json .Mounts}}' uptime-kuma

Compose gives the container a generated name; docker ps shows it. Use it wherever this guide says uptime-kuma, and run the commands as root.

PathBack up?What it is
kuma.dbYes, with .backup or while stoppedThe SQLite database: monitors, heartbeats, notifications, status pages, users, settings
kuma.db-wal, kuma.db-shmOnly in a stopped copySQLite's write-ahead log and its index. Uptime Kuma runs SQLite in WAL mode, so recent changes wait here for a checkpoint.
db-config.jsonYesWhich database to use. For external MariaDB it holds the host, user and password in plain text.
upload/YesStatus page logos
docker-tls/YesCertificates and keys you placed for remote Docker hosts, one folder per host
mariadb/, run/mariadb/ only while stoppedEmbedded MariaDB's data files, and its socket
screenshots/NoThe latest screenshot of each browser-engine monitor, replaced on its next check

Notification settings, with their tokens and passwords, are stored in the database as plain JSON, and so are 2FA secrets. Treat every backup as a secret and encrypt it before it leaves the server. Keep your compose.yaml or docker run command too: environment variables and extra mounts aren't in the data folder.

The JSON export is gone

Version 1 had a Backup page under Settings, with a JSON export and import. Version 1 already marked it deprecated, saying it "cannot generate or restore a complete backup", and version 2 removed it. The project's migration guide says backing up the data directory is currently the only supported backup method. Version 2 can't import those old JSON files.

Before choosing a method, check which database you use:

Terminal
docker exec uptime-kuma cat /app/data/db-config.json

"type": "sqlite" is the common case. embedded-mariadb is the MariaDB server built into the full image, with its files in /app/data/mariadb; the slim image doesn't have it. mariadb is an external MariaDB or MySQL server.

The simplest backup: stop, archive, start

This works for every database type, and it's what the project tells you to do before upgrading. On SIGTERM, Uptime Kuma stops its monitors, checkpoints the WAL into kuma.db and shuts down embedded MariaDB, allowing itself 30 seconds. Docker kills a Linux container after 10 by default, so give it the full 30:

Terminal
install -d -m 700 /var/backups/uptime-kuma
Terminal
docker stop -t 30 uptime-kuma
Terminal
tar -czf /var/backups/uptime-kuma/uptime-kuma-$(date +%F).tar.gz -C <data-folder> .
Terminal
docker start uptime-kuma

<data-folder> is the Source path from docker inspect. With Compose, run docker compose stop -t 30 and docker compose start in the compose folder instead. Nothing is monitored while the container is stopped, so do it at a quiet hour.

Copy SQLite while Uptime Kuma runs

To avoid the gap, copy kuma.db with SQLite's backup API. It produces a consistent copy, including changes still in the WAL, while Uptime Kuma keeps writing. Copying kuma.db with cp or tar can miss those changes or catch a write halfway; SQLite backups explains why. The image ships the sqlite3 tool, so there's nothing to install.

This script makes the copy inside the container, checks it, streams one archive of the copy plus the rest of the folder to the host, and keeps 14 days:

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

CONTAINER="uptime-kuma"
BACKUP_DIR="/var/backups/uptime-kuma"
KEEP_DAYS=14
STAGE="/tmp/kuma-stage"
OUT="$BACKUP_DIR/uptime-kuma-$(date +%Y-%m-%d_%H%M).tar.gz"

mkdir -p "$BACKUP_DIR"
trap 'rm -f "$OUT.partial"; docker exec "$CONTAINER" rm -rf "$STAGE" || true' EXIT

docker exec "$CONTAINER" rm -rf "$STAGE"
docker exec "$CONTAINER" mkdir -p "$STAGE"
docker exec "$CONTAINER" sqlite3 /app/data/kuma.db ".timeout 10000" ".backup '$STAGE/kuma.db'"

result=$(docker exec "$CONTAINER" sqlite3 "$STAGE/kuma.db" "PRAGMA integrity_check;")
if [ "$result" != "ok" ]; then
  echo "integrity_check failed: $result" >&2
  exit 1
fi

docker exec "$CONTAINER" tar -czf - \
  -C "$STAGE" kuma.db \
  -C /app/data --exclude='./kuma.db*' --exclude=./mariadb --exclude=./run --exclude=./screenshots . \
  > "$OUT.partial"

tar -tzf "$OUT.partial" > /dev/null
mv "$OUT.partial" "$OUT"

find "$BACKUP_DIR" -name 'uptime-kuma-*.tar.gz' -type f -mtime +"$KEEP_DAYS" -delete
  • .timeout 10000 waits up to 10 seconds for a lock instead of failing at once. PRAGMA integrity_check prints ok for a healthy copy.
  • The excludes skip the live database files, embedded MariaDB's files and the screenshots. The checked copy goes in as kuma.db.
  • docker exec has no -t here: with a terminal attached, the archive stream gets mangled.
  • tar -tzf reads the whole archive back before it replaces the .partial name, so a cut-off file never looks like a backup.

Make it executable with chmod 755 and schedule it:

/etc/cron.d/uptime-kuma-backup
15 3 * * * root /usr/local/bin/uptime-kuma-backup.sh >> /var/log/uptime-kuma-backup.log 2>&1

Then copy /var/backups/uptime-kuma to storage in another account, for example with rclone.

MariaDB setups

External MariaDB or MySQL: dump the database with its own tool, and archive the data folder as well for db-config.json, upload/ and docker-tls/. The host, user and database name are in db-config.json:

Terminal
mariadb-dump -h <db-host> -u <db-user> -p --single-transaction <db-name> > /var/backups/uptime-kuma/kuma.sql

--single-transaction takes a consistent InnoDB snapshot without stopping Uptime Kuma, and -p prompts for the password. mysqldump takes the same options; the mysqldump guide shows how to script the password.

Embedded MariaDB: use stop, archive, start. The project documents no dump procedure for the built-in server, and mariadb/ copied while it runs may not start again.

Restore onto a new server

Restore onto the same version the backup came from. A newer version migrates the database forward when it starts; from version 1 to 2 that can take hours on a large database and must not be interrupted. Find the running version:

Terminal
docker exec uptime-kuma node -p "require('/app/package.json').version"

On the new server, with Docker installed, unpack the archive into an empty folder:

Terminal
mkdir -p /opt/uptime-kuma/data
Terminal
tar -xzf uptime-kuma-2026-10-04_0315.tar.gz -C /opt/uptime-kuma/data
/opt/uptime-kuma/compose.yaml
services:
  uptime-kuma:
    image: louislam/uptime-kuma:2.5.5
    restart: unless-stopped
    volumes:
      - ./data:/app/data
    ports:
      - "3001:3001"

Start it from /opt/uptime-kuma and watch the log:

Terminal
docker compose up -d
Terminal
docker compose logs -f
  • Log in with the same username and password as before; users are in the database.
  • If db-config.json is missing but kuma.db is there, Uptime Kuma assumes SQLite and writes the file itself.
  • External MariaDB: load the dump first, and edit hostname and password in db-config.json if they changed. UPTIME_KUMA_DB_* environment variables override the file.
  • Embedded MariaDB needs the full image (2), not 2-slim.
  • Rootless images (2-rootless) need the folder owned by 1000:1000: chown -R 1000:1000 /opt/uptime-kuma/data.
  • Keep the folder on a local disk. The README says NFS is not supported.

Stop the old instance before the new one starts checking, or every alert goes out twice. Push monitors keep working only once their URL's host name points at the new server.

Test a restore without sending alerts

A restored copy starts every active monitor and sends notifications at once. If you use Uptime Kuma's built-in Cloudflare Tunnel, it also connects with the same tunnel token. So for a SQLite backup, pause the copy's monitors and remove the token before it starts, using the image's own sqlite3:

Terminal
mkdir -p /root/kuma-test/data
Terminal
tar -xzf /var/backups/uptime-kuma/uptime-kuma-2026-10-04_0315.tar.gz -C /root/kuma-test/data
Terminal
docker run --rm -v /root/kuma-test/data:/app/data louislam/uptime-kuma:2.5.5 sqlite3 /app/data/kuma.db "PRAGMA integrity_check;" "UPDATE monitor SET active = 0;" "DELETE FROM setting WHERE key = 'cloudflaredTunnelToken';"
Terminal
docker run -d --name kuma-restore-test -p 127.0.0.1:3002:3001 -v /root/kuma-test/data:/app/data louislam/uptime-kuma:2.5.5

Uptime Kuma only starts monitors marked active, so nothing is checked or sent. Port 3002 listens on localhost only; reach it from your computer through an SSH tunnel, then open http://localhost:3002 and log in:

Terminal (your computer)
ssh -L 3002:127.0.0.1:3002 root@<server-ip>

Check that monitors (all paused), notifications and status pages are there. Compare the monitor count with production, and read the newest heartbeat's time, which tells you how old the backup is:

Terminal
docker exec kuma-restore-test sqlite3 /app/data/kuma.db "SELECT COUNT(*) FROM monitor;" "SELECT MAX(time) FROM heartbeat;"
Terminal
docker rm -f kuma-restore-test
Terminal
rm -rf /root/kuma-test

Common errors

SymptomFix
The database setup page ("Which database would you like to use?") appears instead of the loginThe container can't see your restored folder. Check the mount with docker inspect and the path you unpacked to.
database is locked during .backupRaise .timeout, or run the backup when fewer monitors are writing.
integrity_check prints anything but okDon't keep that copy. Take it again; if the live database fails the same check, restore from an older backup.
A rootless container won't start after a restoreSet the folder's owner to 1000:1000.
Duplicate alerts after a restoreTwo instances are running. Stop one, or pause the copy's monitors as shown above.
Embedded MariaDB won't start from a backupThe archive was taken while it ran. Use a stop-and-archive backup.

Frequently asked questions

Where does Uptime Kuma store its data?
In one data folder: /app/data inside the Docker image, ./data in the install folder otherwise, or wherever DATA_DIR points. With SQLite, the database is kuma.db in that folder.
Can I export Uptime Kuma monitors to JSON?
Not in version 2. The JSON backup was deprecated in version 1 and removed in 2.0. Back up the data folder instead.
Can I copy kuma.db while Uptime Kuma is running?
Not with cp. Use sqlite3 kuma.db ".backup ...", which goes through SQLite's locking and includes the WAL, or stop the container first.
Can I restore a version 1 backup into version 2?
Yes. Version 2 migrates a version 1 data folder when it starts. It can take a long time, and if it's interrupted you restore the backup and start again.
Can I move from SQLite to MariaDB?
Not directly. The project's migration guide says it isn't supported and points to third-party tools, warning that one common converter doesn't create all the indexes Uptime Kuma needs.

How this was checked

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