Browse the manual
Backups
Create authenticated backups and recover into an empty replacement installation. Preserve the source, application key and newer records.
On this page
For: owners and admins protecting their installation. Where: Settings > Backups.
Create and download backups here. Recovery uses a separate installation through the command line. The browser does not replace an existing database.
Creating a backup
- Enter an optional Passphrase.
- Click Create Backup Now.
- Wait for the completed file to appear.
- Click Download beside the file.
Keep an encrypted copy outside the server. A backup on the same disk does not protect against disk failure.
Existing backups
The list shows each ZIP file, creation date and size. Download saves a copy. Delete removes the server copy after confirmation.
The owner and admin can create, list, download and delete backups. Other roles cannot access them.
Scheduled backups
Choose Disabled, Daily (02:00) or Weekly (Sunday 02:00), then click Save Settings. The default is Disabled.
The server must run php artisan schedule:run every minute. Scheduled backups use the application timezone (APP_TIMEZONE). Keep cron output to detect failures.
Keep last N backups accepts 1 to 100; the default is 5. Successful command-line and scheduled backups apply this limit. Manual browser backups do not.
Retention removes recognized backup ZIPs only. It never removes invoice records, issued originals, audit rows or email delivery history from the application.
What backups include
| Entry | Content |
|---|---|
database.sql |
Database schema and data, including users, invoices, payments, settings and audit history |
media/private/ |
Private application files, including issued PDF and XML originals |
media/public/ |
Uploaded public files, including logos |
manifest.json |
Version, migration fingerprint, snapshot time, file sizes and SHA-256 hashes |
manifest.hmac |
Manifest authentication using the original installation key |
Backups use format 2. Recovery checks every file before import. A checksum or successful download alone does not prove recovery will succeed.
What backups do not include
Backups exclude application code, .env, APP_KEY and server configuration. Keep the original .env and application version in a separate encrypted secret store.
Do not generate a new APP_KEY for recovery. Recovery needs the original key to authenticate the backup and preserve encrypted data.
Downloading backups
Store downloaded backups outside the server. Restrict access: the database contains the business's financial and customer records.
An online backup is a recovery point. It does not include later changes and does not authorize removing them.
Server requirements
Creation needs PHP proc_open() and mysqldump; recovery also needs mysql. The MariaDB names mariadb-dump and mariadb also work. PHP must have ZIP and Sodium support. Use MySQL 8.4, or MariaDB 10.11, 11.4 or 11.8.
A backup records its database engine. It restores only on the same engine: a MySQL backup on MySQL, a MariaDB backup on MariaDB. Recovery refuses a different engine and changes nothing.
The database account needs access to its own database. Dump creation uses a transaction and excludes tablespaces; it needs no global PROCESS privilege.
Keep space for the staged files and archive. Recovery requires twice the expanded archive size plus 16 MiB of free staging space.
Limits are 50,000 archive entries, 2 GiB expanded data and 1 GiB per file. The manifest limit is 2 MiB. Backup archives cannot contain symlinks.
A dump timeout after 5 minutes, failed client command or failed write stops creation. An incomplete archive never appears as a completed backup.
Restoring from a backup
Recovery requires shell access, an empty database on the same engine and a separate application directory. Ask your host for assistance if these are unavailable.
Keep the existing installation intact. Do not run the web installer on the recovery destination; it would populate the database.
- Obtain the trusted application package that created the backup.
- Extract it into a separate directory with empty private and public media directories.
- Copy the saved
.envinto that directory. - Change its database connection to a new, empty database on the same engine as the backup.
- Keep its original
APP_KEY. - Set
MAIL_MAILER=logfor verification. - Leave cron and queue workers stopped on the replacement.
- Enter maintenance mode from the replacement directory:
php artisan down
- Create the public-media link in the replacement:
php artisan storage:link
This links public/storage to this replacement's storage/app/public directory. Keep the storage directories as real directories. Do not link to the source installation.
Stop if the command fails or an existing link points elsewhere. Ask your host to resolve it before recovery.
- Restore the backup from its secure location:
php artisan recovery:restore /secure/backups/backup.zip --encrypted
Use --encrypted to enter the backup passphrase at a hidden prompt. Omit it for an unencrypted archive.
- Run the verification command:
php artisan recovery:verify
The command verifies migrations, the application key and issued-original hashes. A failed check returns a nonzero exit code. Missing or changed originals are listed by artifact ID and state.
The replacement stays in maintenance mode after success. The JSON receipt records the snapshot time, version, counts and whether this was a final snapshot.
Restore risks
Recovery refuses a populated destination, wrong key, wrong passphrase, incompatible version or schema, and missing or altered files.
It validates every required manifest field before extracting files or importing SQL. An authenticated archive with incomplete or malformed metadata also fails. Older unsigned backups cannot use this command.
If import stops halfway, keep that destination in maintenance mode. Inspect the failure or provision another empty destination. A retry never overwrites partial data.
Do not switch traffic to an older snapshot until you account for records created after it. Restoring cannot reconstruct missing payments or issued invoices.
How to test restore safely
Use a disposable replacement with its own database and storage. Keep mail on log and leave all scheduled work stopped.
Compare invoice numbers, exact balances, customers and original downloads with the source. For example, verify both a EUR 125.00 invoice and a USD 240.00 invoice.
After activation, check that the saved logo loads through /storage/ and that private originals remain accessible through their authorized downloads.
Confirm that existing users can sign in after the planned activation. Use a temporary host address restricted to the operator during this check.
Keep the original installation and snapshot until the recovery meets your business's retention policy.
Final snapshot and cutover
Stop and drain source requests and jobs before creating the final sealed backup. Restore that snapshot into an empty replacement.
| Verification result | What to do |
|---|---|
| Succeeds | Switch traffic after the final checks below. Enable the replacement. Keep the old source stopped |
| Fails | Keep the replacement in maintenance mode. Resolve the failure before continuing |
- Block new requests at the host and enable maintenance mode on the source.
- Stop cron and queue workers.
- Wait for running requests and jobs to finish.
- Create the final snapshot:
php artisan backup:create --sealed --confirm-drained --encrypted
The confirmation records your assertion that all writers stopped. Maintenance mode alone cannot prove this.
- Restore that snapshot into an empty replacement using the steps above.
- Run
recovery:verifyand check that the receipt sayssealed: true. - Check invoice numbers, balances, original downloads and the recorded snapshot time.
- Restore the intended mail configuration on the replacement.
- Switch the host's document root or traffic route to the replacement.
- Run
php artisan upon the replacement and restart its scheduler and workers. - Check that the saved logo and other public uploads load from the replacement.
Keep the source stopped. If it received new writes after the snapshot, prepare a new final snapshot and replacement before switching.
After the replacement receives writes, never switch back to the stale source. Stop writes and prepare a forward recovery containing the newer records.
Encryption
An optional passphrase applies AES-256 encryption to every ZIP entry. Store that passphrase separately from the archive. Losing it prevents recovery.
Scheduled backups are unencrypted. Protect the server directory and encrypt the off-site copy. For an interactive encrypted backup, use php artisan backup:create --encrypted.
--passphrase remains available for existing scripts, but its value can appear in process arguments. Prefer the hidden prompt when running a command yourself.
Shared-hosting permissions
Backup and temporary directories must be writable by the PHP account. Completed backups use private file permissions.
Replacement recovery also requires a separate database, a separate directory, and control over cron and the document root. Confirm these capabilities with your host.
Permissions
Owner and admin manage browser backups. Shell commands require trusted server access. Keep .env, private files and backups outside the public document root.
Replacement updates for server operators
Use this advanced procedure when the Update page is unavailable or the host cannot run a dashboard update. Normal updates use the dashboard.
You need shell access, a separate directory and an empty database on the same engine as the source. Ask your hosting provider to perform these steps if needed.
The release package must support your installed version. Keep the original application, environment and backups intact. A package that refuses your version cannot convert it through this procedure.
Verify the release
Download the release from the Made with Pepper purchase or support channel. Use an existing trusted installation to check the ZIP before extracting it:
php artisan release:verify /secure/releases/made-with-pepper.zip
Verification checks the release requirements and every file's size and SHA-256 digest against the included manifest.
Test packages, files that differ from the manifest, extra files, unsafe paths and symlinks refuse.
Release packages need no signing key. These checks detect damaged or changed files; they do not prove who supplied the package.
Use your trusted purchase or support channel. If you cannot verify the ZIP, ask your hosting provider or Made with Pepper support before proceeding.
Do not proceed after a nonzero exit. Keep the package digest and verification output with your maintenance record.
Prepare the replacement
- Prepare: verify the downloaded release. Stop and drain source writers. Create the final sealed backup. Prepare an empty replacement.
- Restore: recovery checks source compatibility before restoring the snapshot and migrating the replacement.
- Verify: check the replacement. A failed check leaves it in maintenance mode.
- Switch: move traffic only after verification succeeds. Keep the source stopped.
- Stop source requests and scheduled workers.
- Wait for running work to finish.
- Create a final sealed backup.
- Keep the original installation stopped and intact.
- Extract the verified release into a separate directory.
- Copy the original
.env, including its unchangedAPP_KEY. - Point the replacement at a new, empty database on the same engine as the source.
- Set
MAIL_MAILER=log. Scheduled work must remain stopped there. - Enter maintenance mode in the replacement directory:
php artisan down
- Create the replacement's public-media link before recovery:
php artisan storage:link
The supported link is public/storage to this replacement's storage/app/public directory. Its storage directories must be real directories.
Target file verification permits this local link. It rejects other runtime links and links to another installation. Release ZIPs cannot contain symlinks.
Stop if link creation fails or an existing link points elsewhere. Ask your host to resolve it before continuing.
Do not run the web installer. It would create data and make the destination ineligible for recovery.
Restore and migrate
For a supported upgrade, run:
php artisan recovery:restore /secure/backups/final.zip --encrypted --upgrade
php artisan recovery:verify
Omit --encrypted only for an unencrypted archive. The hidden prompt asks for the archive passphrase.
The command checks target files against the manifest and verifies the declared predecessor before import. It then imports the backup, copies media and runs migrations on the replacement.
A failed migration returns nonzero. The destination stays in maintenance mode and receives no successful verification receipt.
For same-version disaster recovery, omit --upgrade and use the backup recovery procedure.
Verify and switch
Check the receipt's snapshot time and sealed: true. Compare invoice numbers, exact balances, users and issued-original downloads with the source.
Restore the intended mail settings. Switch the host's document root or traffic route. Run php artisan up on the replacement. Restart its scheduled workers.
Check that the saved logo and other public uploads load through the replacement's /storage/ path. Keep private files outside that directory.
Keep the source stopped. If any source write occurred after the final snapshot, prepare a new backup and replacement before switching.
Recovery after a failure
Confirm where each write occurred before changing traffic. The replacement's newer records determine the recovery path.
flowchart TD
accTitle: Recovery before and after replacement writes
accDescr: Before replacement writes, the untouched source remains available for recovery. After replacement writes, stop writes and recover forward with the newer records. Do not return to the stale source or overwrite a partial destination.
writes{"`Any writes on
the replacement?`"}
writes -->|No| source["`Original source
available`"]
writes -->|Yes| stop["Stop writes"]
stop --> forward["`Recover forward
with newer
records`"]
forward --> stale["`Keep the stale
source stopped`"]
Before the replacement receives writes, the untouched source remains available for recovery. Confirm which installation received each write before changing traffic.
After the replacement receives writes, do not switch back to the stale source. Stop writes and prepare a forward recovery containing the newer records.
A partial destination is never overwritten by retry. Keep it for diagnosis or prepare another empty destination.
Custom code and backups
Keep custom code in version control. An altered target fails manifest verification. Rehearse custom changes through a reviewed deployment procedure.
Retain the trusted application package, backup, original .env, key and operator record. A backup ZIP alone is not a complete recovery set.
Related pages
Need help with the product?
Contact support