GLPI Backup and Disaster Recovery: The Definitive Guide

Production playbook for GLPI backup and disaster recovery: consistent dump, the glpicrypt.key almost everyone forgets, rotation, a rehearsed restore test and an RPO/RTO decision matrix.

A backup that was never restored is hope, not strategy - and in GLPI the part that most often breaks the restore is not the database dump, it is glpicrypt.key. While maintaining client environments we have seen a "perfect" database restore where nobody could log in via LDAP or send email anymore: the data came back, but the key that decrypts the LDAP, SMTP and mail collector passwords was left behind. This guide consolidates the backup, rotation, restore-testing and disaster recovery routine we run in production, focusing on the points where things actually break.

What really has to go into the backup

GLPI has three layers of state, and all three must come back together and consistent:

  • Database - tickets, assets, users, rules and settings. It is the heart, but on its own it does not rebuild the environment.
  • Files directory (files/) - attached documents (files/_documents), images, inventory XML and plugin data. If the database has the document row but the file did not come back, the user clicks and gets an error.
  • Configuration (config/) - the config_db.php (host, database and credentials) and, above all, glpicrypt.key. That key decrypts the passwords stored in the database: LDAP bind, mail collector, notification SMTP and OAuth secrets. Restoring the database without it leaves all those passwords as undecryptable garbage.

Inside files/ not everything needs a backup. Volatile directories such as _cache, _tmp, _sessions, _cron and _lock are regenerated on their own and only bloat the backup - exclude them. What matters is preserving _documents, _pictures, _uploads, _inventories and _plugins.

A consistent database dump

GLPI 10 and 11 use InnoDB on every table, so --single-transaction takes a consistent snapshot without locking the app - users keep opening tickets during the backup. --quick avoids loading huge tables into memory and --default-character-set=utf8mb4 prevents corruption of accents and emojis. Never pass the password on the command line (it shows up in ps and in logs); use a restricted credentials file:

# ~/.my.cnf (chmod 600) - keeps the password out of 'ps' and logs
[client]
host = 127.0.0.1
user = glpi
password = ...glpi_password...

The native GLPI backup (Administration > Maintenance) writes a .sql to files/_dumps, but on large databases it tends to blow past PHP time and memory limits. For production, a scheduled mysqldump is more reliable.

Backup script with rotation

A single script that handles the database, files and config in the right order and still cleans up anything older than 30 days:

#!/usr/bin/env bash
set -euo pipefail

# Credentials in ~/.my.cnf (chmod 600), never on the command line
DATA=$(date +%F)
DEST="/backup/glpi/${DATA}"
mkdir -p "${DEST}"

# Database: consistent InnoDB snapshot without locking the app
mysqldump --single-transaction --quick --routines --triggers \
  --default-character-set=utf8mb4 glpi \
  | gzip -6 > "${DEST}/glpi_db.sql.gz"

# Files + config AFTER the dump (an extra file on disk is harmless;
# a DB row pointing to a missing file is not)
tar -czf "${DEST}/glpi_files.tar.gz" \
  --exclude='files/_cache' --exclude='files/_tmp' \
  --exclude='files/_sessions' --exclude='files/_lock' \
  -C /var/www/glpi files config

# Rotation: keep 30 days
find /backup/glpi -maxdepth 1 -type d -mtime +30 -exec rm -rf {} +

echo "Backup OK: ${DEST}"

Notice the order: the dump comes before the tar. A file that lands on disk after the dump is harmless on restore; the opposite - a database row pointing to a file that did not yet exist when tar ran - becomes a broken attachment. Schedule it in cron:

# /etc/cron.d/glpi-backup - daily at 02:15
15 2 * * * root /opt/scripts/backup-glpi.sh >> /var/log/glpi-backup.log 2>&1

Decision matrix: RPO vs RTO

Before you settle frequency and retention, answer two questions: how much data the business can afford to lose (RPO) and how quickly it must be back up (RTO). The answer changes with criticality:

Environment profileTarget RPOTarget RTODatabase frequencyRetentionOffsite
Staging / test24 hdaysdaily7 daysoptional
Standard production24 h4 hdaily14-30 daysyes (cloud)
Critical production (SLA)15 min1 hdaily + binlog or replica30-90 daysyes, encrypted

These are honest ranges, not promises. A 15-minute RPO requires MariaDB/MySQL binlog or a replica - the daily dump alone will not deliver it.

Restore testing: the step almost nobody does

A backup that was never restored is a number in a report, not a guarantee. Rehearse the restore periodically in a throwaway environment - Docker is ideal - and never on top of production:

# THROWAWAY environment (Docker). Never rehearse on top of production.
mysql -u root -e "CREATE DATABASE glpi_restore CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci"

# 1) Database
zcat glpi_db.sql.gz | mysql --default-character-set=utf8mb4 -u root glpi_restore

# 2) Files + config (INCLUDES config/glpicrypt.key)
tar -xzf glpi_files.tar.gz -C /var/www/glpi

# 3) Code newer than the dump? apply the schema migration
php bin/console db:update --no-interaction

# 4) Clear the cache and start up to validate login/LDAP/email
php bin/console cache:clear

After the restore, confirm the state matches reality before you trust it. Two quick queries already catch the most common problems:

-- Version stored in the database (must match the code before db:update)
SELECT value AS db_version
FROM glpi_configs
WHERE context = 'core' AND name = 'version';

-- How many documents point to a physical file in files/_documents
SELECT COUNT(*) AS docs_with_file
FROM glpi_documents
WHERE filepath <> '';

The first shows the version stored in the database: if it is lower than the code, db:update is what reconciles the schema. The second counts documents that expect a file on disk - compare it with what actually came in files/_documents. In Docker, the same steps run with docker exec -u www-data glpi php bin/console ....

Real disaster recovery

A backup is a command; disaster recovery is a plan. The healthy minimum is the 3-2-1 rule: three copies, on two different media, one of them offsite. And here lies the common mistake: dropping the database dump and config/ (with glpicrypt.key) into the same .tar, unencrypted, and shipping it to a bucket. Whoever gets that file holds the entire database and the key that decrypts the LDAP, SMTP and collector passwords - that is, the client's infrastructure credentials in recoverable text. Offsite backups must go encrypted (for example with age or gpg) and with restricted access.

Close the plan with a written runbook - where the backups are, the restore order, who triggers it - and with the RTO measured in a real rehearsal. In maintenance work, what separates a scare from a catastrophe was never having a backup; it was having restored that backup beforehand, clock in hand, and knowing it comes back in 40 minutes and not in "I don't know".

If your GLPI is critical and you have never timed a full restore - database, files and the encryption key together - NexTool designs and operates the backup and disaster recovery routine for your environment, with a rehearsed restore and encrypted offsite copies. Talk to us about GLPI support and maintenance.


This content was produced with the assistance of artificial intelligence and reviewed by the Nextool Solutions team.

Frequently Asked Questions

Use mysqldump --single-transaction: since GLPI 10 and 11 are all InnoDB, the dump is consistent without locking the app, and users keep opening tickets. Back up the files (files/) and the configuration right afterward, in the same cron-scheduled script.

It is the key that decrypts the passwords stored in the database: LDAP bind, mail collector, notification SMTP and OAuth secrets. If you restore the database but lost config/glpicrypt.key, those passwords become undecryptable garbage and LDAP login and email sending stop. Always back up config/ together with the database.

Database: daily at a minimum for production. Files: daily or weekly, depending on the volume of attachments. For a short RPO (15 to 30 minutes) you need MariaDB binlog or a replica; the daily dump alone gives an RPO of up to 24 hours.

Restore it periodically in a throwaway environment (Docker), never on top of production. Start GLPI, log in, open an old ticket with an attachment and check LDAP and email. Time the process: that time is your real RTO. A backup that was never restored is not a guarantee.

Yes, as long as the code is equal to or newer than the dump. Restore the database, then run php bin/console db:update to migrate the schema. Never restore a newer dump onto older code: the structure does not roll back and the environment breaks.

Same principle: docker exec into the database container for mysqldump and tar the mounted volumes for files/ and config/. Pin the image version (do not use :latest) so the restore lands on code compatible with the dump.

Need help?