WordPress Backup Plugins: Limitations and Best Use
WordPress backup plugins are convenient. They are often the wrong tool for reliable, daily, production-grade backups on a VPS. Most plugins default to full exports that run under PHP, store artifacts in or near the webroot, and do not provide the same verification and operational control as server-level tooling.
- Many plugins create a full archive on every run (inefficient for daily backups).
- Backups created inside WordPress run under PHP limits (timeouts, memory limits, web server limits).
- Storing backups under
wp-content/is a common security foot-gun. - Plugins can still be useful for migrations and one-click manual snapshots.
- For VPS environments, prefer server-level backups (tar/zstd + mysqldump + offsite copy + verification).
What most backup plugins actually do
Many backup plugins follow this pattern:
- walk the WordPress filesystem
- export database to SQL
- compress everything into a ZIP-like artifact
- store the archive somewhere under
wp-content/ - optionally upload that archive to cloud storage
This approach is simple. It is also why it often becomes inefficient at scale.
Why full daily exports are inefficient
The key inefficiency is repeated work. If your site is 10 GB and you run a full export daily, you can end up producing and uploading 10 GB artifacts even if only a few MB changed.
Storage and bandwidth math
Example assumptions:
- site files: 10 GB
- database dump (compressed): 500 MB
- daily full plugin export: 10.5 GB
Monthly impact:
10.5 GB/day * 30 days = 315 GB of uploads per month
315 GB stored (or more, depending on retention)
Even if your cloud storage is cheap, your VPS I/O and CPU time are not free. Backups can become the busiest workload on the server.
Restores are not automatically better
Full archives are easier than incremental chains in theory. In practice, plugin restores can fail mid-flight due to timeouts or limits. That turns "simple" into "unreliable".
Where plugin backups break on VPS hosts
PHP execution limits
Plugin backups run in the same environment as your application. They inherit PHP limits like:
max_execution_timememory_limit- FastCGI/web server timeouts
- request body size limits during restore
Failure symptoms:
- backup stuck at a percentage
- HTTP 500 during backup
- restore stalls at a percentage
Disk I/O pressure
Compressing large directory trees causes disk reads and writes. On busy WordPress servers, this can increase latency:
- PHP-FPM workers slow down
- MySQL queries slow down
- page TTFB increases
If you run the backup during peak traffic, you can create a self-inflicted outage.
Temporary storage and double-write
Many plugins:
- export DB to an intermediate file
- compress files into an archive
That often means you need extra free disk space during the run. If the filesystem is near full, backups fail.
Shared hosting assumptions
Plugins are often designed to work without SSH. That pushes them toward strategies that are portable but not optimal for VPS operations.
Security risks you should take seriously
Backups inside the webroot
Some plugins store archives under:
wp-content/uploads/wp-content/ai1wm-backups/wp-content/wpvividbackups/wp-content/updraft/
If those paths are web-accessible, your backups can be downloaded. Backups commonly contain:
- the full database
wp-config.php- salts and keys
- sometimes server logs
Mitigations:
- store backup artifacts outside
/var/www/html - restrict permissions
- deny HTTP access (web server rules)
- encrypt before offsite upload
If an attacker can download your backup archive, assume the site is compromised. Change passwords, rotate keys, and treat it as a full incident.
Remote storage credentials
If a plugin stores long-lived cloud credentials and the site is compromised, attackers may:
- download backups
- delete backups
- use your cloud resources for abuse
For offsite storage, prefer credentials that are:
- scoped to a single bucket/folder
- write-only when possible
- easy to rotate
Feature gaps (what plugins often do not do well)
Not all plugins are the same. Use this section as an evaluation checklist.
Incremental file backups
Many plugins do not implement true incremental file snapshots.
On a VPS, server-level tools can do it efficiently with rsync deltas.
Incremental database backups
Incremental DB backups require binlogs, PITR, or replication. Most plugins do not provide this.
Smart excludes
Excluding churn matters. Cache folders and nested backup archives inflate plugin backups.
Compression control
On VPS hosts, the best format for operational backups is often zstd.
Plugins usually default to ZIP-like formats.
Verification and restore drills
Many plugins focus on "backup creation". Operationally, you want:
- integrity tests (can it be decompressed?)
- content tests (does it contain what you expect?)
- restore drills (does it restore cleanly?)
Retention policies
Backups fill disks. Retention must be explicit and tested.
Plugin vs server-level: a capability matrix
This is a general comparison. Some plugins do better than others.
| Capability | Plugin backups (typical) | Server-level backups |
|---|---|---|
| Runs without SSH | yes | no (usually) |
| Full backup on demand | yes | yes |
| Efficient daily file deltas | often no | yes (rsync, snapshots) |
| DB dump control | limited | strong (mysqldump, mariadb-dump) |
| Compression choice | limited | strong (zstd, gzip, xz) |
| Store outside webroot | sometimes hard | easy |
| Fine-grained excludes | limited | strong |
| Offsite sync efficiency | limited | strong (rclone, rsync) |
| Restore reliability at scale | varies | strong (if scripted) |
| Auditable logs | varies | strong |
| Encryption | varies | strong (gpg, age) |
When plugins are a good fit
Plugins can be the right tool when:
- you are on shared hosting without SSH
- you need a one-click migration package
- you want a manual "before change" snapshot
- your site is small and you can test restores easily
Common good uses:
- migration/cloning
- ad-hoc snapshots before risky changes
- non-technical operators who need a simple restore UI
When plugins are risky
Be cautious if:
- your site is large (multi-GB)
- you run WooCommerce (transactional DB changes)
- you have strict uptime requirements
- backups must run unattended on a schedule
In these cases, plugin limitations become production risk.
A server-level strategy (efficient and safer)
This is the core idea:
- DB: frequent full dumps (compressed)
- Files: snapshot-style backups (rsync deltas)
- Baseline: periodic full archives
- Offsite: copy artifacts outside the VPS failure domain
- Verification: integrity tests + restore drills
Tiered schedule example
| Artifact | Frequency | Why |
|---|---|---|
| DB dump | every 1-6 hours | reduces data loss window |
| file snapshot | daily | captures new uploads and changes |
| full archive | weekly | a simple baseline restore point |
| offsite copy | after each run | survivability |
Example commands (reference)
Files (tar.zst):
sudo tar -C /var/www/html -cf - . \
| zstd -3 -T0 -o "/backups/wp-files-$(date +%F).tar.zst"
Database (zstd):
mysqldump --single-transaction --quick --routines --events --triggers \
--column-statistics=0 wordpress \
| zstd -3 -T0 -o "/backups/wp-db-$(date +%F).sql.zst"
Offsite copy:
rclone copy /backups remote:wp-backups/site-a
Verification:
zstd -t /backups/wp-files-2026-03-01.tar.zst
zstd -t /backups/wp-db-2026-03-01.sql.zst
See the dedicated docs for full workflows:
opt/docker-data/apps/docusaurus/site/docs/server/linux-server/10-backup-disaster-recovery/backup-logging-and-verification.mdxopt/docker-data/apps/docusaurus/site/docs/server/linux-server/10-backup-disaster-recovery/rsync-local-backup.mdxopt/docker-data/apps/docusaurus/site/docs/server/linux-server/10-backup-disaster-recovery/rclone-remote-targets.mdx
Migration plan: plugin to server-level
Use this sequence to avoid downtime.
Inventory current backups
- where does the plugin store archives?
- how often does it run?
- how many versions does it keep?
- have you ever restored from it?
Run both in parallel temporarily
Keep plugin backups while you stand up server-level backups. Do not remove the plugin schedule until you complete at least one restore drill from the new system.
Implement server-level artifacts
- create
/backupswith correct permissions - create file archive + DB dump
- verify
- copy offsite
- verify offsite
Run a restore drill
Restore into staging directories/databases.
sudo rm -rf /tmp/restore-test
sudo mkdir -p /tmp/restore-test
sudo tar --use-compress-program=zstd -xf /backups/wp-files-2026-03-01.tar.zst -C /tmp/restore-test
sudo find /tmp/restore-test -maxdepth 3 -type d -name wp-content -print
Switch plugins to "manual only" (optional)
If you still want the plugin UI:
- disable scheduled exports
- keep it for manual snapshots/migrations
- store outputs outside webroot if possible
If you must use a plugin, make it less dangerous
These practices reduce risk even if you keep plugin backups.
Store backups outside the webroot
If the plugin supports a custom backup path, choose something like /backups/plugin/.
If it does not, use web server rules to block access to the backup directory.
Reduce backup impact
- run off-peak
- exclude caches
- keep retention small locally
- move artifacts offsite quickly
Validate restores
At least monthly:
- download one plugin archive
- restore in a staging environment
- document the steps
Deep dive: why PHP-based compression is fragile
PHP runtime limits and how they fail
Typical constraints:
- PHP worker timeouts can kill long-running backups.
- Memory limits can crash compression streams.
- Web server request limits can block restore uploads.
Symptoms:
- partial archives
- missing files
- restore fails halfway
Safer pattern on VPS:
- run backups from cron/systemd
- use OS tools (
tar,zstd,mysqldump) - log exit codes
Deep dive: why backups under wp-content are risky
Common plugin backup paths and why they get exposed
Common paths:
wp-content/uploads/wp-content/ai1wm-backups/wp-content/wpvividbackups/wp-content/updraft/
Why they get exposed:
- uploads are often publicly readable
- directory listings may be enabled
- backup filenames are guessable
Mitigation:
- store outside webroot
- block access in web server config
- encrypt archives
Troubleshooting
Plugin backups stuck at a percentage
Common causes:
- filesystem near full
- PHP timeout
- memory limit
- huge number of files
Immediate actions:
- check disk space/inodes
- move backups off-peak
- reduce scope (wp-content only)
- migrate to server-level
df -hT
df -ih
Restore fails due to upload limits
Common causes:
- request size limits
- web server timeouts
On VPS, restore from the filesystem instead of through an HTTP upload.
Backups are huge
- exclude caches
- avoid nested backups
- avoid including existing archives
See:
opt/docker-data/apps/docusaurus/site/docs/server/linux-server/10-backup-disaster-recovery/excluding-folders.mdx
Practice lab
Use this lab to evaluate whether your plugin backups are usable.
- Locate the plugin backup directory.
- Copy one backup artifact to a safe location outside the webroot.
- Verify you can extract it.
- Verify the DB dump exists.
- Restore into staging.
sudo ls -lah /var/www/html/wp-content | sed -n '1,200p'
sudo find /var/www/html/wp-content -maxdepth 2 -type d -iname '*backup*' -print
sudo rm -rf /tmp/restore-test
sudo mkdir -p /tmp/restore-test
# Replace archive name with your plugin artifact
sudo unzip /path/to/plugin-backup.zip -d /tmp/restore-test
sudo find /tmp/restore-test -maxdepth 3 -type d -name wp-content -print
Do not restore into /var/www/html until you have validated the artifact in staging.
Plugin evaluation checklist (use this before you trust a plugin)
This checklist helps you evaluate whether a plugin can meet operational requirements.
If you cannot answer "yes" to the critical items, treat the plugin as a migration tool, not as your backup system.
Backup scope
| Check | Why it matters |
|---|---|
| Can you back up files and DB separately? | DB and files have different change rates |
Can you back up only wp-content/ intentionally? | reduces size when core is reproducible |
| Can you exclude caches and temporary directories? | prevents unnecessary growth |
Can you exclude nested archives (*.zip, *.tar*)? | avoids backup recursion |
Storage placement
| Check | Why it matters |
|---|---|
| Can the plugin write to a path outside the webroot? | prevents backup downloads via HTTP |
| Are backups protected from directory listing and direct URL access? | reduces exposure |
Can you enforce restrictive permissions (0600/0700)? | backups often contain secrets |
Performance controls
| Check | Why it matters |
|---|---|
| Can you schedule backups off-peak? | reduces user impact |
| Can you control compression level/format? | CPU/I/O cost varies |
| Can the job run without PHP timeouts? | reliability of unattended runs |
| Can the plugin resume or retry safely? | prevents partial artifacts |
Offsite storage
| Check | Why it matters |
|---|---|
| Offsite upload uses robust transport | avoids silent failures |
| Offsite credentials are scoped and rotatable | reduces blast radius |
| Offsite deletion is controlled | prevents accidental purge |
| You can verify remote copies | avoids "local only" failure |
Verification and restore
| Check | Why it matters |
|---|---|
| Integrity verification exists | detect corrupt archives |
| You can extract/restore without the plugin UI | restores in emergencies |
| Restore drills are documented and repeatable | actual RTO measurement |
Observe resource impact (so backups do not become outages)
If you suspect plugin backups are hurting performance, observe CPU, memory, and I/O during a run.
uptime
free -h
vmstat 1 5
ps -eo pid,user,pcpu,pmem,etime,cmd --sort=-pcpu | head -n 15
ps -eo pid,user,pcpu,pmem,etime,cmd --sort=-pmem | head -n 15
If wa (I/O wait) is high and the site slows down during backup runs, move the workload out of PHP and into scheduled server-level tooling.
Block plugin backup directories from HTTP
If you cannot relocate plugin backup paths outside the webroot, at least block them.
Nginx example
location ~* /(wp-content/(ai1wm-backups|wpvividbackups|updraft|backups))/ {
deny all;
return 403;
}
location ~* \.(zip|tar|gz|zst|wpress)$ {
deny all;
return 403;
}
Apache example
<DirectoryMatch "wp-content/(ai1wm-backups|wpvividbackups|updraft|backups)">
Require all denied
</DirectoryMatch>
<FilesMatch "\.(zip|tar|gz|zst|wpress)$">
Require all denied
</FilesMatch>
Blocking HTTP access reduces risk, but it does not solve the performance/reliability limits of PHP-based backups.
Reference: a complete server-level backup script (files + DB + offsite)
This example is intentionally verbose. Use it as a reference, then split responsibilities into smaller scripts if you prefer.
#!/usr/bin/env bash
set -euo pipefail
umask 077
SITE_ROOT="/var/www/html"
DB_NAME="wordpress"
BACKUP_DIR="/backups"
LOG_DIR="$BACKUP_DIR/logs"
STAMP="$(date +%F)"
HOST="$(hostname)"
FILES_ARCHIVE="$BACKUP_DIR/wp-files-${HOST}-${STAMP}.tar.zst"
DB_DUMP="$BACKUP_DIR/wp-db-${HOST}-${STAMP}.sql.zst"
LOG_FILE="$LOG_DIR/wp-backup-${HOST}-${STAMP}.log"
REMOTE="remote:wp-backups/site-a"
mkdir -p "$BACKUP_DIR" "$LOG_DIR"
{
echo "[$(date -Is)] start"
echo "host=$HOST"
echo "site_root=$SITE_ROOT"
echo "db=$DB_NAME"
echo "files_archive=$FILES_ARCHIVE"
echo "db_dump=$DB_DUMP"
echo "[$(date -Is)] create files archive"
tar -C "$SITE_ROOT" \
--exclude='wp-content/cache' \
--exclude='wp-content/*/cache' \
--exclude='wp-content/updraft' \
-cf - . \
| zstd -3 -T0 -o "$FILES_ARCHIVE"
echo "[$(date -Is)] create db dump"
mysqldump \
--single-transaction \
--quick \
--routines --events --triggers \
--column-statistics=0 \
"$DB_NAME" \
| zstd -3 -T0 -o "$DB_DUMP"
echo "[$(date -Is)] verify integrity"
zstd -t "$FILES_ARCHIVE"
zstd -t "$DB_DUMP"
echo "[$(date -Is)] list archive (spot check)"
tar --use-compress-program=zstd -tf "$FILES_ARCHIVE" | sed -n '1,25p'
echo "[$(date -Is)] artifact sizes"
ls -lh "$FILES_ARCHIVE" "$DB_DUMP"
echo "[$(date -Is)] offsite copy"
rclone copy "$BACKUP_DIR" "$REMOTE" \
--include "wp-files-${HOST}-*.tar.zst" \
--include "wp-db-${HOST}-*.sql.zst" \
--include "wp-backup-${HOST}-*.log"
echo "[$(date -Is)] verify offsite listing"
rclone lsf "$REMOTE" | sed -n '1,40p'
echo "[$(date -Is)] retention (local)"
find "$BACKUP_DIR" -maxdepth 1 -type f -name "wp-files-${HOST}-*.tar.zst" -mtime +30 -delete
find "$BACKUP_DIR" -maxdepth 1 -type f -name "wp-db-${HOST}-*.sql.zst" -mtime +30 -delete
find "$LOG_DIR" -type f -name "wp-backup-${HOST}-*.log" -mtime +30 -delete
echo "[$(date -Is)] done"
} | tee "$LOG_FILE"
The retention lines permanently delete files. Run list-only versions first when adapting this script.
Restore testing pattern (what to prove)
If you want to trust backups during an incident, prove these in staging:
- you can extract the files archive
- the WordPress layout is present
- you can import the DB dump into a staging DB
- you can start services and load wp-login
set -e
sudo rm -rf /tmp/restore-test
sudo mkdir -p /tmp/restore-test
sudo tar --use-compress-program=zstd -xf /backups/wp-files-2026-03-01.tar.zst -C /tmp/restore-test
sudo find /tmp/restore-test -maxdepth 3 -type d -name wp-content -print
sudo find /tmp/restore-test -maxdepth 3 -type f -name wp-config.php -print
zstd -dc /backups/wp-db-2026-03-01.sql.zst | mysql wordpress_restore
Decision guide
Use this as a fast "what should I do" chart.
| Your situation | Recommended approach |
|---|---|
| Shared hosting, no SSH | plugin backups + offsite copies, frequent restore drills |
| VPS, low-change site | server-level full backups + offsite |
| VPS, large media site | server-level snapshots + offsite, avoid PHP compression |
| WooCommerce | frequent DB dumps + snapshots + offsite, consider HA for low RTO |
| Strict uptime requirements | HA design + backups + drills |
Next steps
- Backup types and schedules:
opt/docker-data/apps/docusaurus/site/docs/server/linux-server/10-backup-disaster-recovery/full-vs-incremental-vs-differential.mdx. - 3-2-1 design:
opt/docker-data/apps/docusaurus/site/docs/server/linux-server/10-backup-disaster-recovery/321-backup-strategy-for-wp.mdx. - Logging and verification:
opt/docker-data/apps/docusaurus/site/docs/server/linux-server/10-backup-disaster-recovery/backup-logging-and-verification.mdx. - Disaster recovery workflow:
opt/docker-data/apps/docusaurus/site/docs/server/linux-server/10-backup-disaster-recovery/disaster-recovery-workflow.mdx.