Skip to content

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:

  1. Put the site in maintenance mode (core.maintenance_mode).
  2. Unpack the db backup with gunzip and load it into the database (mysql ... < backup.sql).
  3. For a files / full backup, extract the files into the project root.
  4. Empty cp-core/var/cache/prod/, run cp: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.

Protect the token

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.

Last updated: 13.10.2026