Guides Guide

PrestaShop Backup & Disaster Recovery: Complete Guide

Practical backup strategy for PrestaShop — mysqldump, file backups, automated cron jobs, offsite storage, test restores, and disaster recovery plans.

Why we run nightly backups on every shop we touch

We've been shipping PrestaShop modules since 2014, and we've had to restore from backup more times than we'd like to admit. Database corruption after a botched migration. A junior dev who accidentally rm -rf'd a vendor directory on a live shop. A module install that left ps_configuration in a state PrestaShop couldn't load past. One memorable Tuesday at 3 AM where we discovered a half-written mysqldump from the night before, with the table list cut off mid-row because the disk had filled up. That's the one that taught us to check exit codes.

Every dev/staging shop we run has automated nightly backups going to off-server storage. Not because we expect things to break — because we've watched them break enough times to know the cost of the alternative.

A backup is only as good as your last successful restore test. We had a client whose hosting provider's "automatic daily backup" turned out to be three days old when we needed it — the cron had been silently failing for a week. Treat untested backups as theoretical, not real.

What's actually worth backing up

The database (the only truly irreplaceable thing)

Orders, customers, products, configuration, cart rules, employee accounts, the lot. Files we can reinstall from git and module ZIPs. The database is the business — lose it and you've lost the shop.

Files that matter

  • /modules/ (installed modules, custom configs, uploaded assets. Don't assume you can re-download every module) paid modules tied to a specific licence can be slow to recover
  • /themes/ — your active theme and any child theme. We've seen weeks of theme work vaporised because nobody backed up themes/mypresta-rocks/
  • /img/ — usually the largest directory, often 10GB+ on a mature shop. Product photos, category banners, CMS images
  • /upload/ — attachments and virtual product files (the ones customers download after purchase)
  • /override/ — every custom override you'd otherwise need to remember and re-write
  • app/config/parameters.php (PS 1.7+) or config/settings.inc.php (PS 1.6) — DB credentials and, more importantly, the cookie/crypto key. Lose this and all encrypted data in the DB becomes garbage
  • /mails/ and /translations/ — only if you've customised them
  • .htaccess, nginx vhosts, php.ini overrides, cron definitions

What to skip

  • var/cache/ — regenerates on its own. Backing it up wastes space and slows restores
  • var/logs/ — handy for forensics, useless for recovery
  • vendor/composer install rebuilds it. (A caveat for us: we ship synced packages into vendor/myprestarocks/, so if you're in our world, those do need backing up because they aren't in composer.json.)
  • node_modules/ — never. Just never
  • var/sessions/ — transient

How we dump databases

mysqldump, with the flags that matter

This is what runs on every shop we maintain:

mysqldump \\
  --single-transaction \\
  --quick \\
  --lock-tables=false \\
  --routines \\
  --triggers \\
  --events \\
  -u YOUR_DB_USER \\
  -p'YOUR_DB_PASSWORD' \\
  YOUR_DB_NAME > prestashop_$(date +%Y%m%d_%H%M%S).sql

Why each flag is non-optional on a live shop:

  • --single-transaction gives you a consistent InnoDB snapshot without locking. Without it, mysqldump locks tables and your checkout hangs for as long as the dump runs. We've seen it hang for 90 seconds on a mid-size catalogue — long enough for customers to bail and never come back
  • --quick streams rows instead of loading whole tables into memory. On a 5GB ps_orders + ps_order_detail, this is the difference between "dump finishes" and "mysqldump OOM-killed"
  • --lock-tables=false matters because some older PS installations still have MyISAM tables hiding in modules — the default behaviour locks them
  • --routines --triggers --events — PrestaShop core doesn't ship triggers, but plenty of modules add them (and we have a strict rule against ever dropping a trigger we didn't write, because we've been bitten). Skip these flags and you'll discover at restore time that a module's trigger is missing and the shop misbehaves in a way that takes hours to diagnose

phpMyAdmin export — fine for small shops, painful for big ones

On shared hosting with no SSH, this is what you have. Pick Custom, enable gzip, tick "Add DROP TABLE". The hard limit: anything over 100MB risks timing out, and there's no way to resume. We've watched 5-minute exports get cut off at 4:30 with no warning.

PrestaShop's built-in DB Backup tool

At Advanced Parameters → DB Backup. Convenient for a quick "snapshot before I change this setting" backup, and that's it. It skips triggers and routines, can timeout on large databases, stores the file on the same server (defeating most of the point of backups), and in some PS versions produces dumps that are silently incomplete. Use it as a supplement to a real backup, never as your only strategy.

Compress, always

SQL dumps compress 80–90%. Piping straight through gzip saves disk and bandwidth:

# Backup + compress in one step
mysqldump --single-transaction --quick --lock-tables=false \\
  -u USER -p'PASS' prestashop | gzip > backup_$(date +%Y%m%d).sql.gz

# Restore from compressed backup
gunzip < backup_20260228.sql.gz | mysql -u USER -p'PASS' prestashop

Automated daily backup with rotation

This is roughly the script we drop on shops we manage. Seven daily, four weekly, twelve monthly:

#!/bin/bash
# /home/user/scripts/backup-db.sh

DB_USER="your_db_user"
DB_PASS="your_db_password"
DB_NAME="prestashop"
BACKUP_DIR="/home/user/backups/database"
DATE=$(date +%Y%m%d_%H%M%S)
DAY_OF_WEEK=$(date +%u)
DAY_OF_MONTH=$(date +%d)

mkdir -p "$BACKUP_DIR"/{daily,weekly,monthly}

# Take the backup
mysqldump --single-transaction --quick --lock-tables=false \\
  --routines --triggers \\
  -u "$DB_USER" -p"$DB_PASS" "$DB_NAME" \\
  | gzip > "$BACKUP_DIR/daily/prestashop_${DATE}.sql.gz"

if [ $? -ne 0 ]; then
    echo "BACKUP FAILED at $(date)" | mail -s "BACKUP FAILED" your@email.com
    exit 1
fi

# Weekly snapshot (Sunday)
[ "$DAY_OF_WEEK" -eq 7 ] && cp "$BACKUP_DIR/daily/prestashop_${DATE}.sql.gz" \\
  "$BACKUP_DIR/weekly/prestashop_weekly_${DATE}.sql.gz"

# Monthly snapshot (1st of month)
[ "$DAY_OF_MONTH" -eq "01" ] && cp "$BACKUP_DIR/daily/prestashop_${DATE}.sql.gz" \\
  "$BACKUP_DIR/monthly/prestashop_monthly_${DATE}.sql.gz"

# Rotation
find "$BACKUP_DIR/daily/"   -name "*.sql.gz" -mtime +7   -delete
find "$BACKUP_DIR/weekly/"  -name "*.sql.gz" -mtime +28  -delete
find "$BACKUP_DIR/monthly/" -name "*.sql.gz" -mtime +365 -delete
# Add to crontab — runs daily at 3:00 AM
0 3 * * * /home/user/scripts/backup-db.sh >> /home/user/logs/backup.log 2>&1

The exit-code check and email alert are the parts most people skip. They're also the parts that catch silent failures — and silent failures are the only kind that matter, because the loud ones get fixed the next morning.

Files: tar for the full picture, rsync for incremental

Full tarball

# Full backup excluding unnecessary directories
tar -czf prestashop_files_$(date +%Y%m%d).tar.gz \\
  --exclude='var/cache/*' --exclude='var/logs/*' \\
  --exclude='var/sessions/*' --exclude='node_modules' \\
  /var/www/html/

# Critical files only (faster, smaller)
tar -czf prestashop_critical_$(date +%Y%m%d).tar.gz \\
  /var/www/html/{modules,themes,img,upload,override,mails,.htaccess} \\
  /var/www/html/app/config/parameters.php

Incremental sync with rsync

Once your img/ directory is north of 5GB, full tarballs every night become wasteful. rsync only copies what changed, which on a mature catalogue is usually a handful of new product photos:

# Local incremental sync
rsync -avz --delete \\
  --exclude='var/cache/' --exclude='var/logs/' --exclude='var/sessions/' \\
  /var/www/html/ /home/user/backups/files/current/

# Remote sync (off-site — much safer)
rsync -avz --delete \\
  --exclude='var/cache/' --exclude='var/logs/' \\
  -e "ssh -p 22" \\
  /var/www/html/ backupuser@remote-server:/backups/prestashop/

One gotcha we've been burned by: --delete with rsync is unforgiving. If a directory is missing on the source for any reason (failed mount, typo in the path), --delete will happily wipe the corresponding directory on the destination. We now exclude runtime-generated directories like sitemaps/, backup/, and module-specific upload paths explicitly, because a botched source side shouldn't be able to delete data that only exists on the backup.

Off-server storage is the only kind that counts

The first rule

If your backups live on the same server as your shop, they aren't backups — they're a slightly inconvenient version of "no backup". Disk failure, ransomware, a hosting provider going bankrupt, a rogue rm -rf / in the wrong terminal — all of these take out the primary and the "backup" simultaneously. Your backup must live somewhere that can survive the loss of the primary.

Where we put things

# Amazon S3
aws s3 sync /home/user/backups/ s3://your-bucket/prestashop/ --storage-class STANDARD_IA

# Backblaze B2 (cheaper than S3, excellent for backups)
b2 sync /home/user/backups/ b2://your-bucket/prestashop/

# Google Cloud Storage
gsutil cp backup.sql.gz gs://your-bucket/prestashop/database/

# SCP/rsync to another server
rsync -avz -e ssh /home/user/backups/ backupuser@backup-server:/backups/prestashop/

We use a mix: nightly DB dumps go to Backblaze B2 (cheap, S3-compatible, US/EU options), shop files rsync to our TrueNAS box, and the truly critical shops also get a weekly S3-compatible snapshot in a different region. Container snapshots on the Docker host catch the "I broke the container" case, but they live on the same machine, so they're not a substitute for off-site.

3-2-1

Three copies, two storage types, one off-site. For a PrestaShop shop: the live site, a local backup on a separate disk, and a cloud backup in a different region. Easy to remember, hard to argue with after you've had to use it.

Encryption for anything with customer data

Database dumps contain personal data. Under GDPR you're on the hook for protecting it in backups too — not just in production. We use GPG symmetric encryption for everything that leaves the shop's own filesystem:

# Encrypt with GPG
mysqldump --single-transaction --quick --lock-tables=false \\
  -u USER -p'PASS' prestashop | gzip \\
  | gpg --symmetric --cipher-algo AES256 --batch --passphrase "STRONG_PASSPHRASE" \\
  > backup_$(date +%Y%m%d).sql.gz.gpg

# Decrypt and restore
gpg --decrypt --batch --passphrase "STRONG_PASSPHRASE" backup.sql.gz.gpg \\
  | gunzip | mysql -u USER -p'PASS' prestashop
Store the passphrase in a password manager. Not on the server. Not in the backup script. Not in a comment. We've personally seen "encrypted backup, passphrase lost" — the encrypted files were as recoverable as random noise.

Testing your backups (the part everyone skips)

An untested backup is a guess. The failures that only show up at restore time: truncated SQL dumps where the disk ran out mid-write, zero-byte files because the DB password rotated three weeks ago, MySQL 8 dumps that won't load on MySQL 5.7, missing file permissions, excluded directories you didn't realise were critical, changed encryption keys.

The "incomplete dump" scenario is the one that haunts us. The script ran, the file exists, the file even has the right approximate size — and then on restore you find out the dump cut off at row 4 million of ps_search_word because /tmp filled up.

How we actually test

# Create a test database and restore
mysql -u root -p -e "CREATE DATABASE prestashop_test;"
gunzip < backup.sql.gz | mysql -u root -p prestashop_test

# Verify data completeness
mysql -u root -p prestashop_test -e "
  SELECT COUNT(*) AS products FROM ps_product;
  SELECT COUNT(*) AS orders FROM ps_orders;
  SELECT COUNT(*) AS customers FROM ps_customer;
  SELECT MAX(date_add) AS latest_order FROM ps_orders;
"

For a full test: extract the file tarball into a staging container, swap parameters.php to point at the test DB, hit the homepage, log into the Back Office, open a product, try to add to cart. We do this quarterly on every shop we maintain. The first time you do it on a new client, expect to find something — we almost always do.

Disaster recovery: write it down before you need it

Write these down. Print them. Keep a copy somewhere you can read when the server is unreachable. We learned this the time we needed to restore a shop and discovered the only copy of the runbook was on the shop itself.

Scenario 1: shop completely down

  1. Confirm the scope. Is it the server, the host's network, DNS, or Cloudflare? dig and traceroute before you panic
  2. Contact the host. Get an ETA. Confirm they still have your data
  3. If recovery isn't coming: provision a new server with the same OS, PHP, and MySQL versions. PHP 8.1 dumps won't import cleanly on MySQL 5.7
  4. Restore files from your off-site backup, not the one that lived on the dead server
  5. Restore the database from your most recent off-site dump
  6. Update DNS if the IP changed (and remember TTL — pre-lower it during planned migrations)
  7. Verify front office, Back Office, checkout, transactional email

Scenario 2: database corrupted

  1. Stop the web server: systemctl stop apache2 (or nginx, or your container)
  2. Try repair first: mysqlcheck -u root -p --auto-repair prestashop
  3. If repair fails, restore from backup:
    mysql -u root -p -e "DROP DATABASE prestashop; CREATE DATABASE prestashop;"
    gunzip < latest_backup.sql.gz | mysql -u root -p prestashop
  4. Restart the web server, clear PrestaShop's cache (var/cache/) and OPcache
  5. Compare the latest ps_orders.id_order to what you remember. That's your data-loss window

Scenario 3: hacked, files modified

  1. Take the site offline. Now, not after you "have a look"
  2. Preserve evidence: tar -czf hacked_site.tar.gz /var/www/html/ — you'll want this for forensics and possibly for law enforcement
  3. Find the entry point:
    find /var/www/html/ -name "*.php" -mtime -7 -ls
    grep -rl "eval(base64_decode" /var/www/html/
    grep -rl "shell_exec" /var/www/html/modules/
  4. Restore files from a backup dated before the breach. The trick: you usually don't know exactly when the breach happened. Cross-reference modified timestamps with your backup dates and pick something older than the earliest suspicious modification
  5. Check the DB for rogue admin accounts (SELECT * FROM ps_employee WHERE date_add > '...') and changed config (ps_configuration rows touched recently)
  6. Rotate every credential: DB, admin passwords, FTP, SSH keys, Webservice API keys, mail SMTP
  7. Update PrestaShop core and every module to current versions before bringing the shop back up
  8. Monitor for two to four weeks. Attackers leave multiple backdoors — we've cleaned up shops where the third one didn't surface until a month later

Scenario 4: someone deleted a module folder

  1. Extract just what's missing:
    tar -xzf prestashop_files_backup.tar.gz \\
      --directory /var/www/html/ var/www/html/modules/your_module_name/
  2. If the module was properly uninstalled via Back Office (DB tables and config gone), restore the DB too or reinstall the module from the original ZIP
  3. Clear cache, fix permissions if the restore ran as the wrong user

RTO and RPO — set them honestly

  • RTO — how long you can afford to be down. A 4-hour RTO means your full restore process completes inside 4 hours. A 15-minute RTO requires hot standby, not backups
  • RPO — how much data you can afford to lose. Daily backups = up to 24 hours of lost orders. A shop doing 100+ orders/day should be aiming for 1–4 hour RPO via binlog replay

At 100 orders/day and 50 EUR average value, an hour of downtime costs ~200 EUR in direct revenue alone, ignoring SEO impact and refund-request blowback.

Point-in-time recovery with binlogs

Daily backups leave a gap: if your bulk import went wrong at 2 PM and your last backup was 3 AM, you've lost 11 hours of orders. MySQL binary logs let you replay every committed transaction up to a specific moment.

Enable binary logging

# /etc/mysql/mysql.conf.d/mysqld.cnf
[mysqld]
log_bin = /var/log/mysql/mysql-bin
binlog_expire_logs_seconds = 604800
max_binlog_size = 100M
binlog_format = ROW
systemctl restart mysql
mysql -u root -p -e "SHOW VARIABLES LIKE 'log_bin';"  # Should show ON

Roll back to a specific minute

# 1. Restore the most recent full backup (from 3:00 AM)
gunzip < daily_backup.sql.gz | mysql -u root -p prestashop

# 2. Replay binlogs up to the moment BEFORE the problem (e.g., 14:29)
mysqlbinlog \\
  --start-datetime="2026-02-28 03:00:00" \\
  --stop-datetime="2026-02-28 14:29:00" \\
  /var/log/mysql/mysql-bin.000042 /var/log/mysql/mysql-bin.000043 \\
  | mysql -u root -p prestashop

This recovers the database state from one minute before the incident. The two cases where we've actually used it: a botched product import that nuked descriptions on 8,000 SKUs, and a module update that ran a migration with a missing WHERE clause.

Binlogs cost disk. A busy shop generates hundreds of MB per day. Seven days of retention is our default — long enough to catch most "we discovered it Monday" incidents, short enough not to fill the disk.

Don't trust your host's backups

Hosts advertise "daily backups". The reality, in our experience:

  • "Regular backups" often means weekly, sometimes weekly with a generous interpretation of "weekly"
  • Restore requests can cost 50–200 EUR each and take 24–48 hours to fulfil
  • Granular restore (one table, one file) is almost never an option — it's all or nothing
  • Retention is usually 2–3 copies, so by the time you notice an issue, the clean version may already be gone
  • Most ToS include "we are not liable for data loss" — read yours

Verify it: ask the host what's backed up, how often, where, and for how long. Then request a test restore to a temporary directory. If they can't or won't, you have your answer. We had a client whose "daily backups" turned out to be a stale weekly snapshot — discovered only when we asked for a restore to a staging site and the file we got back was 11 days old.

Treat host backups as a bonus, never your primary strategy. Your data, your responsibility.

The checklist we keep pinned

Daily (automated)

  • ☐ DB backup runs via cron and produces a file with non-zero size and exit code 0
  • ☐ Backup is compressed and copied off-server
  • ☐ Daily rotation keeps 7 days

Weekly (automated)

  • ☐ File backup (full or rsync) runs
  • ☐ Weekly DB snapshot preserved (keep 4)
  • ☐ Off-site sync verified

Monthly (automated + a 10-minute manual check)

  • ☐ Monthly DB snapshot preserved (keep 12)
  • ☐ Look at cron logs — backups still running?
  • ☐ Off-site storage has recent files?
  • ☐ Disk space on backup target?

Quarterly (manual, on the calendar)

  • Full test restore to a staging environment
  • ☐ Counts match: products, orders, customers
  • ☐ Back Office logs in, products render, checkout works
  • ☐ Note how long the restore took — that's your real RTO
  • ☐ Review and update the disaster recovery doc

Before any major change

  • ☐ Manual backup before PrestaShop core upgrades
  • ☐ Manual backup before module installs or updates
  • ☐ Manual backup before bulk imports or migrations
  • ☐ Manual backup before server-config changes

Security

  • ☐ Backups containing customer data are encrypted
  • ☐ Passphrase in a password manager, not on the server
  • ☐ Off-site storage uses SSH keys or scoped API tokens
  • ☐ Backup scripts read credentials from ~/.my.cnf, not inline
  • ☐ Backup files are chmod 600

Tip: credentials out of scripts

# Create ~/.my.cnf so backup scripts don't contain plaintext passwords
cat > ~/.my.cnf << 'EOF'
[mysqldump]
user=your_db_user
password=your_db_password
[mysql]
user=your_db_user
password=your_db_password
EOF
chmod 600 ~/.my.cnf

# Now mysqldump works without -u and -p flags
mysqldump --single-transaction --quick --lock-tables=false prestashop | gzip > backup.sql.gz

The minimum we'd accept on a shop we run

Automated daily DB dumps with 7-day retention. Weekly file backups. At least one off-site copy. A quarterly restore test that someone actually does. A written DR plan that lives somewhere other than the production server. Anything beyond that (binlog replay, encrypted off-site, multi-region) scales with the value of the shop.

The hour to set this up is now, on a calm Tuesday afternoon. Not 3 AM after a migration goes wrong.

Related reading

Loading...
Back to top