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/) - theconfig_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 profile | Target RPO | Target RTO | Database frequency | Retention | Offsite |
|---|---|---|---|---|---|
| Staging / test | 24 h | days | daily | 7 days | optional |
| Standard production | 24 h | 4 h | daily | 14-30 days | yes (cloud) |
| Critical production (SLA) | 15 min | 1 h | daily + binlog or replica | 30-90 days | yes, 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.