Backup and maintenance
Backup
Backups are taken from the AACP → Maintenance → Backup screen (permission: system.backup.manage) or from the command line:
php cp-core/bin/console cp:backup:create db # database only (gzipped SQL dump)
php cp-core/bin/console cp:backup:create files # files (zip)
php cp-core/bin/console cp:backup:create full # database + files
Backups are stored under cp-core/var/backups/. The file name carries the type and time.
What is left out of the backup
The file backup deliberately leaves out: old backups (cp-core/var/backups/), regenerable cache (cp-core/var/cache/), session files, the Tailwind build folder, the page cache (public/page-cache/), .git, node_modules and user home directory leftovers (.ssh, .config…). Vendor and public/ are included, so no extra installation step is needed to return from a backup. Files that might carry secrets are also filtered out.
A backup on the same server is not a backup
A backup sitting on the same disk as the server protects against a bad deployment and a deleted table; but not against the event people actually take backups for, which is losing the server. That is why CPalius offers a shipper (BackupShipper) that takes the backup off the server. The targets are the same as for media: S3-compatible, Cloudflare R2 and FTP; see Media and storage.
| Setting | Default | |
|---|---|---|
storage.backup.target |
off |
The target backups are sent to |
storage.backup.prefix |
cpalius-backups |
Remote folder/prefix |
storage.backup.keep_local |
on | Keep the local copy after sending |
storage.backup.remote_retention |
10 | How many newest backups to keep remotely |
Two design decisions are worth knowing:
Failure is loud. In media offload, a failed remote copy is a warning. In backup it comes as an exception onto the operator's screen. The whole value of the feature is that the operator believes it works; a remote backup that quietly stopped working eleven months ago is worse than having none, because it has bought a year of not looking.
The local copy is deleted last and only on evidence. On 5 GB shared hosting keep_local = false is a legitimate choice, but "upload then delete" is safe only if the upload has been verified. That is why the local file is deleted only after the object has been read back and verified; a PUT that returned 200 but was written incompletely cannot destroy the only sound copy. In addition, a target is not used as a backup target without passing the live write test.
The record of what was sent is kept in a settings row (no extra table/migration is needed); it also works on an installation whose files are up to date but whose schema is not yet updated.
Restoring
Restoring is not an automatic "one click"; it is a deliberate operator procedure:
- Put the site in maintenance mode (
core.maintenance_mode). - Unpack the
dbbackup withgunzipand load it into the database (mysql ... < backup.sql). - For a
files/fullbackup, extract the files into the project root. - Empty
cp-core/var/cache/prod/, runcp:doctor.
For the automatic snapshot taken during an update and automatic rollback, see Update system.
Health check
cp:doctor (details on the Installation page) is not a substitute for a backup but catches silent corruption: does the table a module migration says it created really exist, has an undefined capability been left in roles, are the translation keys equal. Run it regularly (for example after every deployment).
cp:security:audit scores the security posture; see Security.
Logs
AACP → Maintenance → Logs (system.logs.manage):
| Log | What |
|---|---|
| Application log | Monolog; above logging.min_level (default warning), kept for logging.retention_days (30) |
| Module quarantine log | cp-core/var/log/module_quarantine.log: failing hooks, modules |
| Email log | cp_mail_logs; resending, mail.log_retention_days (90) |
| Audit log | Administrator actions; audit.retention_days (365) |
| Security telemetry | WAF/login events; 3–90 days depending on severity |
Old rows are cleaned with scheduled tasks (logging.purge, audit.purge). Permanent deletion requires the system.logs.purge capability.
Trash
Deleted records fall into the trash before being permanently deleted; they can be restored. Switching per type and the retention period (default 30 days) are set from AACP → Maintenance → Trash. Details: Content model.
Recovery console
The last refuge of the "the core never dies" principle: /aacp/recovery. It works if a recovery token is defined in .env (if empty, the gate is closed) and opens without a database. From here you can:
- Disable a faulty module,
- Clear the cache.
It depends on no module or theme. The WAF and IP ban also deliberately exempt this address: it is the way back for an operator who has banned themselves.
The recovery token is a powerful authority: generate it long and random (php -r "echo bin2hex(random_bytes(32));"), do not share it, and leave it empty in environments you do not use.
Routine maintenance list
| Frequency | Job |
|---|---|
| After every deployment | cp:doctor; is AACP → Quarantine empty |
| Weekly | Check that the backup is reaching the remote target (look at the last backup date) |
| Monthly | cp:security:audit; unused administrator accounts; apply updates |
| Once a year | A restore rehearsal: actually bring the site up from a backup in a separate environment |
A backup that has not been restored does not count as backed up.