How to back up Grafana
Grafana keeps dashboards, users, data sources, alert rules and nearly everything else in one database: SQLite at /var/lib/grafana/grafana.db by default, or MySQL or PostgreSQL. Back it up with the sqlite3 .backup command or a database dump, together with /etc/grafana/grafana.ini, whose secret_key is needed to decrypt every stored data source password, plus the provisioning and plugins folders. Dashboard JSON exported through the HTTP API is useful to have, but it is not a backup of the server.
What to back up
Paths are those of the deb and rpm packages, which run Grafana as the grafana user under the grafana-server service. The official Docker image uses the same paths inside the container.
| Part | Path | What it holds |
|---|---|---|
| Database | /var/lib/grafana/grafana.db | Dashboards and their version history, folders, users, teams, organizations, permissions, data sources with encrypted secrets, alert rules, contact points, notification policies, library panels, annotations, service accounts |
| Config | /etc/grafana/grafana.ini | secret_key, database and SMTP passwords, sign-in settings |
| Overrides | /etc/default/grafana-server (deb), /etc/sysconfig/grafana-server (rpm) | Paths, and any GF_<SECTION>_<KEY> variables, which override grafana.ini |
| Provisioning | /etc/grafana/provisioning | YAML for data sources, dashboards, alerting and plugins, applied at every start |
| Plugins | /var/lib/grafana/plugins | Installed plugin files |
On a binary install the same parts are conf/custom.ini and data/ in the install folder. If type under [database] is mysql or postgres, the database lives on that server and you dump it instead; Grafana's configuration reference recommends one of those over SQLite in production.
The secret_key
Grafana encrypts the secrets it stores: data source passwords and tokens, contact point credentials. Since v9.0 it uses envelope encryption: each secret is encrypted with a data key kept in the database, and the data keys are encrypted with secret_key from the [security] section. A copy of grafana.db is useless for those secrets without that key.
grep -in 'secret_key' /etc/grafana/grafana.ini /etc/default/grafana-server-i also catches GF_SECURITY_SECRET_KEY. A line starting with ; is commented out, so the default from defaults.ini applies, and a new server has the same default. Grafana 13's defaults.ini also has a [secrets_manager.encryption.secret_key.v1] section with its own secret_key; if you changed either, keep both. In Docker, look for GF_SECURITY_SECRET_KEY in the compose file or .env.
With a different secret_key, Grafana cannot decrypt the passwords and tokens it stored, so data sources and other connections that use them stop working, and Grafana may not say why at startup. Put the original key back; the alternative is re-entering every password.
Copy grafana.db while Grafana runs
Grafana's backup guide says to stop Grafana before copying the SQLite file. To copy it while it runs, use SQLite's backup API through the sqlite3 shell (apt install sqlite3); cp can catch the file mid-write (why).
install -d -m 700 /var/backups/grafanasqlite3 /var/lib/grafana/grafana.db ".timeout 10000" ".backup '/var/backups/grafana/grafana.db'"sqlite3 /var/backups/grafana/grafana.db "PRAGMA integrity_check;"The check prints ok. .timeout 10000 waits up to 10 seconds for Grafana's write lock instead of failing with database is locked; Grafana leaves WAL off by default (wal = false), so a writer briefly blocks readers. With MySQL or PostgreSQL, use mysqldump --single-transaction grafana or pg_dump -Fc grafana (guide). Then archive the files, skipping the live database and its journal:
tar -czf /var/backups/grafana/grafana-files.tar.gz --exclude='grafana.db*' -C / etc/grafana etc/default/grafana-server var/lib/grafana-C / stores relative paths, so the archive unpacks back over /.
Automate it
Both steps as one nightly script, with the copy checked:
#!/usr/bin/env bash
set -euo pipefail
umask 077
OUT=/var/backups/grafana
STAMP=$(date +%F-%H%M)
mkdir -p "$OUT"
sqlite3 /var/lib/grafana/grafana.db ".timeout 10000" ".backup '$OUT/grafana-$STAMP.db'"
if [ "$(sqlite3 "$OUT/grafana-$STAMP.db" 'PRAGMA integrity_check;')" != "ok" ]; then
echo "integrity check failed: $OUT/grafana-$STAMP.db" >&2
exit 1
fi
gzip "$OUT/grafana-$STAMP.db"
rc=0
tar -czf "$OUT/grafana-files-$STAMP.tar.gz" --exclude='grafana.db*' \
-C / etc/grafana etc/default/grafana-server var/lib/grafana || rc=$?
if [ "$rc" -gt 1 ]; then
exit "$rc"
fi
find "$OUT" -maxdepth 1 -name 'grafana-*' -type f -mtime +7 -delete30 3 * * * root /usr/local/bin/grafana-backup.sh >> /var/log/grafana-backup.log 2>&1- The copy is checked before it is compressed; a copy that fails
integrity_checkstops the script. - tar exits with 1 when a file changed while it was read, which happens on a running server; anything higher stops the script.
- Files older than 7 days are deleted only after a successful run. Copy the folder off the machine, for example with rclone; cron schedules covers logging and timing.
Dashboards as code, and why that is not a backup
Provisioned dashboards come from JSON files a provider in /etc/grafana/provisioning/dashboards points at; Grafana reloads them at every start. Keep those files in Git and they restore themselves. To snapshot dashboards made in the UI, create a service account under Administration, Users and access, Service accounts, add a token, save it in /root/.grafana-backup-token (mode 600), and export every dashboard:
#!/usr/bin/env bash
set -euo pipefail
umask 077
GRAFANA_URL="http://localhost:3000"
AUTH="Authorization: Bearer $(cat /root/.grafana-backup-token)"
OUT="/var/backups/grafana/dashboards-$(date +%F)"
mkdir -p "$OUT"
curl -fsS -H "$AUTH" "$GRAFANA_URL/api/search?type=dash-db&limit=5000" |
jq -r '.[] | select(.type == "dash-db") | .uid' |
while read -r uid; do
curl -fsS -H "$AUTH" "$GRAFANA_URL/api/dashboards/uid/$uid" > "$OUT/$uid.json"
done/api/searchreturns only what the token may see, in the token's organization, up tolimit5000 per page (pagefor more)./api/dashboards/uid/<uid>returns{"dashboard": ..., "meta": ...};meta.folderUidnames the folder.curl -fandpipefailmake the script fail on any HTTP error instead of saving an error page.- Grafana 13 deprecates the
/apiendpoints in favor of/apis, but still serves them; the new equivalent is/apis/dashboard.grafana.app/v1/namespaces/default/dashboards/<uid>.
To load one back, put the token in TOKEN, set id to null and post it; overwrite replaces a dashboard with the same uid. The folder must already exist:
jq '{dashboard: (.dashboard | .id = null), folderUid: .meta.folderUid, overwrite: true}' cIBgcSjkk.json | curl -fsS -X POST -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' --data-binary @- http://localhost:3000/api/dashboards/dbThe JSON holds layout, panels, queries and variables. It does not hold users, teams, permissions, folders, data sources, alert rules, contact points, library panels, annotations or version history, and the data source API never returns stored passwords, only which fields are set. Restoring a server needs the database.
Docker installs
The image keeps data in /var/lib/grafana, a named volume such as grafana-storage in Grafana's examples, and runs as UID 472. Settings usually come from GF_* variables, so keep the compose file and .env, plus any mounted grafana.ini and provisioning folder. The image has no sqlite3, so run it on the host against the volume's folder:
sqlite3 /var/lib/docker/volumes/grafana-storage/_data/grafana.db ".timeout 10000" ".backup '/var/backups/grafana/grafana.db'"docker volume inspect grafana-storage shows the real Mountpoint; Compose puts the project name in front of the volume's name. To archive the whole volume, stop the container and use a helper container (Docker volume backups). After a restore, the files must belong to UID 472.
Restore onto a new server
Install the same Grafana version or a newer one. A newer version migrates the database when it starts; Grafana's guides treat going back as restoring the pre-upgrade backup, so never start an older version on it. Stop Grafana, unpack the files, put the database back and fix ownership:
systemctl stop grafana-servertar -xzf grafana-files.tar.gz -C /cp grafana.db /var/lib/grafana/grafana.dbchown -R grafana:grafana /var/lib/grafanasystemctl start grafana-servercurl -s http://localhost:3000/api/healthIn the reply, database should be ok, next to the version. From the nightly script's files, gunzip the .db.gz before copying it. Sign in with the old server's admin password: admin_password in grafana.ini is only used on the very first start. Open a data source and click Save & test; success proves the secret_key matches. With MySQL or PostgreSQL, restore the dump into an empty database before Grafana's first start. Repeat this on a throwaway server now and then (testing restores).
Common errors
| Error | Fix |
|---|---|
GF_PATHS_DATA='/var/lib/grafana' is not writable. | Docker: the restored volume or bind mount belongs to someone else. chown -R 472:0 it. |
attempt to write a readonly database | grafana.db or its folder belongs to root after the copy. Run chown -R grafana:grafana /var/lib/grafana. |
database is locked | The copy waited on Grafana's write lock. Keep .timeout 10000 before .backup, or stop Grafana for the copy. |
| Data sources fail to log in after the restore, with no clearer error | secret_key differs from the old server's. Restore the original key. |
| The admin password from grafana.ini is rejected | The restored database keeps the old password. grafana cli admin reset-admin-password <new-password> sets a new one. |
Could not find config defaults, make sure homepath command line parameter is set or working directory is homepath | You called the binary directly. Add --homepath /usr/share/grafana, or use the package's grafana wrapper, which sets it. |
| Panels report a missing plugin | /var/lib/grafana/plugins was not restored. Restore it or reinstall the same plugin versions. |
| The export script saves no files | The token sees no dashboards: wrong organization, or a role without access to the folders. |
Frequently asked questions
- Where is the Grafana database stored?
- With the deb or rpm package and in Docker, at /var/lib/grafana/grafana.db. On a binary install it is data/grafana.db in the install folder, unless [database] in the config points to MySQL or PostgreSQL.
- How do I back up all Grafana dashboards?
- Back up grafana.db, which holds every dashboard and its history. For portable copies, list them with /api/search and save each one by its uid from /api/dashboards/uid with a service account token.
- What happens if I lose Grafana's secret_key?
- Stored secrets can no longer be decrypted, so data sources and contact points lose their passwords and you re-enter them. Dashboards, users and settings are unaffected.
- Can I restore a Grafana backup on a newer version?
- Yes. Grafana migrates the database forward on its first start. Do not go the other way: Grafana's guides treat a rollback as restoring the backup taken before the upgrade.
How this was checked
Commands, limits and prices were checked against these official pages, on October 4, 2026:
- Grafana documentation: Back up Grafana
- Grafana documentation: Configure Grafana (paths, database, security)
- Grafana documentation: Configure database encryption
- Grafana documentation: Provision Grafana
- Grafana documentation: Folder/Dashboard Search API
- Grafana v11.6 documentation: Dashboard HTTP API (get by uid, create/update)
- Grafana documentation: Dashboard APIs (/apis)
- Grafana documentation: Migrate to the new APIs
- Grafana documentation: HTTP API authentication
- Grafana documentation: Data source HTTP API
- Grafana documentation: Other HTTP API (/api/health)
- Grafana documentation: Grafana CLI
- Grafana documentation: Run Grafana Docker image
- Grafana documentation: Configure a Grafana Docker image
- Grafana documentation: Upgrade strategies (when to upgrade)
- Grafana v13.2.3 source: conf/defaults.ini
- Grafana v13.2.3 source: deb packaging (default file, systemd unit, postinst)
- Grafana v13.2.3 source: packaging/docker/run.sh
- Grafana v13.2.3 source: pkg/api/api.go (dashboard and search routes)
- Grafana v13.2.3 source: decipher_aes.go (AES-CFB decryption)
- SQLite: Command Line Shell (.backup, .timeout)
- SQLite: PRAGMA integrity_check