Sitecarry builds a single package containing your site's files and a full database
export, then lets you download it from the WordPress admin.
The build runs in short, resumable slices, so it works on shared hosting where a
single PHP request is capped at 30 seconds. If a request times out, the next one
picks up exactly where the last one stopped.
Each package comes with a standalone installer that restores it onto any server —
the same file works for moving a site to new hosting, cloning live to staging, or
rolling a site back.
What it does
- Full site package: files plus a complete SQL export
- Your
wp-config.php is never archived, so the database password and the login
keys and salts stay on the server; the restore writes that file itself
- Scheduled backups — hourly, twice daily, daily or weekly, with retention
- Optional incremental file backups, with automatic full backups in between
- Upload to Amazon S3, any S3-compatible storage, FTP or Google Drive
- Restore straight from storage when the archives are no longer on the server
- Email or Slack/Discord notification when a backup fails
- Verify that a package is intact, from the admin or WP-CLI
- Rehearse a backup, by hand or every week — import it into scratch tables, check
the row count, prove the result would work as a site, and
confirm serialized data survives a domain change. Then throw the tables away.
Nothing on the site is touched
- WP-CLI commands for backing up, listing, verifying and checking status
- Backups run in the background — start one and close the tab
- Resumable build and resumable restore — both survive short PHP execution limits
- Live progress throughout
- Standalone
installer.php: no WordPress needed on the target server
- Serialization-safe URL rewriting, so page builder content survives the move
- Skips caches,
node_modules, .git, and other backup plugins' folders, which is
usually the difference between a 400MB package and a 40GB one
- Records a manifest describing the origin site (URLs, table prefix, versions)
Why URL rewriting is the hard part
WordPress stores a lot of settings as PHP serialized data, where every string
carries its own byte length. Replacing an old domain with a longer one using a
plain search and replace breaks those lengths, and WordPress can then no longer
read the value — which is how a migration quietly wipes widget settings and page
builder layouts. Sitecarry rewrites the serialized form directly and repairs the
lengths, and it also handles the JSON-escaped form (
https:\/\/) that page
builders such as Elementor store their content in.
Security
Packages contain a complete copy of your database. Sitecarry stores them in a
protected folder with an unguessable filename, blocks direct web access via
.htaccess and
web.config, and serves downloads only through an authenticated
admin request — never a public URL.
Every package has its own restore passphrase, shown in the admin and kept out of
the archive. The installer refuses to do anything without it, so an installer
left behind on a server cannot be used by anyone else to overwrite the site. It
also deletes itself and the archive when the restore finishes.
1.32.0
- The activity history can now be cleared, from a link in that card. Useful when a
site changes hands, or when the failures in it were all yours from testing. The
packages themselves are untouched — only the record of what has happened.
1.31.2
- Fixed: on sites large enough that a backup runs in more than one pass, the
finished archive could be written with an index length that was short by the
last few entries, and no zip reader would open it — "zip error 21". The length
now comes from the running total the packer keeps rather than from the
filesystem, whose answer can lag behind on some hosts. Verify and rehearse
caught this every time; existing packages that fail to verify should be
replaced with a fresh backup.
1.31.1
- Fixed on Windows servers: each backup included every earlier package, and its
passphrase file, so packages doubled in size with every run. Windows reports the
uploads folder with backslashes, so the check that skips Sitecarry's own folder
never matched. Both paths are now normalised the way WordPress does it. Linux and
macOS servers were not affected.
1.31.0
- Sitecarry now says so plainly on a site whose database it cannot export into a
restorable package. The export is MySQL and the installer restores through
mysqli, so on SQLite — WordPress Playground, or the SQLite integration plugin —
a backup would build and could never be restored. Backups are refused there
instead, with the reason on the screen.
1.30.0
- The restore installer is never written to this server. It is assembled in memory
and streamed straight to the browser, so the generated file exists only as the
download itself.
1.29.0
- wp-config.php is no longer part of a package. It holds the database password
and the eight keys and salts that sign every login cookie, and a package can be
uploaded to storage outside your control. A restore writes that file instead:
onto an existing install it updates the one already there, and into an empty
folder it generates a new one with fresh keys.
- Restoring starts with a download. The Restore button used to write a restore
installer into the WordPress folder; a plugin should not put a program there, so
it does not any more. Each package row now offers Get installer, and the
screen explains the three steps: download the installer and the archive, upload
both to the site being restored, open installer.php. The restore itself is
unchanged, including moving to a new domain, server or table prefix.
wp sitecarry stage is replaced by wp sitecarry installer <id>, which prints
the installer so you can redirect it to a file.
- Loopback requests now pass their SSL verification through core's own
https_local_ssl_verify filter rather than switching it off outright.
- The readme name matches the plugin header.
1.28.0
- Requires WordPress 6.2 or later. Every database query that names a table now passes
the name through
$wpdb->prepare() as an identifier placeholder.
- Inside WordPress, all remote requests go through the WordPress HTTP API. The
standalone restore installer, which runs without WordPress, keeps its own transport.
- The plugin no longer raises PHP's execution time limit. Each step is sized to fit
inside the host's limit and resumes in the next request.
- Settings are sanitized field by field as they are read from the form.
- The restore installer downloads from a file on disk instead of being echoed.
- Plugin constants now use the
SITECARRY_ prefix.
- The package manifest no longer records the content folder path, which nothing read.
1.27.0
- The status strip no longer wraps. Its date is short and the destination drops the
"Uploaded to" prefix the label above it already implied, so the five boxes stay the
same height.
- A backup's restore passphrase now sits beside its Restore button, which is what
asks for it, instead of at the far end of a mostly empty row.
- Settings reads lighter. Each control keeps its first line and folds the reasoning
behind it into a short link, so the screen is a list of settings again rather than
three paragraphs under each one.
1.26.0
The screens, redesigned. Same controls, same words, laid out like what they
are: evidence.
- One state mark — a dot that means good, needs attention, or failed — used the same
way in the status strip, on every package and down the activity timeline, so it is
learned once.
- Facts in a monospace face; sentences in the admin's own. Sizes, counts, passphrases
and log lines read as the records they are, and prose stays prose.
- Each package is now a row with its date, its state, its facts and its two actions —
not six columns and six buttons.
- Recent activity is a timeline with a spine.
- Settings, Storage and Notifications group their controls into cards, each with a
one-line explanation on the left. The help on the Backups screen folds away until
wanted.
- Nothing here loads a font or an image. Accents follow your admin colour scheme;
reds and greens deliberately do not.
1.25.0
- The status strip no longer wraps its fifth item onto a row of its own. It was
built for four, and a fifth arrived with rehearsal reporting — at a 1280px window
the five wanted 1080px inside a strip that measures 1063.
- Plugin URI now points at the plugin's own page rather than the site home page.
- New screenshots, showing the rehearsal, the activity log and the storage set-up
guides — the old ones predated all three.
1.24.0
The rest of the interface pass.
- Progress that keeps moving. A percentage sits still for minutes on a large
site. The screen now says "3,200 of 11,045 files" or "table 8 of 32" beside it.
- Deleting names what it is deleting — when it was taken, how big it is, and, if
either is true, that it has been proven to restore or that no copy exists anywhere
but this server.
- A first run is told what pressing the button does, and the two things worth
doing straight after.
- The progress bar follows your admin colour scheme. Failure red and success
green deliberately do not: a scheme that made failed look like fine would be a bug.
- Laid out for narrow screens at the same 782px WordPress switches its own admin
at, where the More menu now sits in the flow instead of hanging off the edge.
- Screen readers: the status strip is a definition list, the progress bar reports
its value, and results that appear after a click are announced.
1.23.0
- A record of what has actually happened. The Backups screen now shows recent
activity — backups, rehearsals and deletions — with a thirty-day summary above it.
Everything else the plugin keeps lives with its package and disappears when
retention prunes it, so on a site keeping five backups nothing older than five days
had ever been visible. This log outlives the packages it describes.
- One primary action per backup. Six buttons of equal weight put Restore, which
replaces the entire site, next to Delete. Restore now stands alone and the rest fold
into a More menu, with Delete set apart at the bottom of it. The menu is built so it
still opens on a page where JavaScript never ran, because two of its items are
ordinary download links.
- Uninstalling now clears the two cron events the weekly rehearsal added in 1.19.0.
1.22.0
The restore installer's buttons did nothing. Fixed, and now checked on every
build. Anyone who downloaded an installer from version 1.16.0 onwards should
download it again — the archives themselves were always fine.
- A search-and-replace against the installer template in 1.16.0 mangled one line of
its JavaScript. That is a syntax error, so the whole inline script failed to load
and no button on the installer page responded. It survived five releases because
the restore had only ever been proven by driving its endpoints directly, never by
opening the page a person actually uses.
- The self-check now assembles the installer exactly as the plugin ships it, lints
it as PHP, pulls out its script and runs it through a JavaScript parser. All three
fail on the old bug, so it cannot come back quietly.
1.21.1
- The Backups screen now shows what is inside an archive: the site files under
www/, the whole database as database.sql beside it, and the manifest. Looking
for the database among the site files is the usual reason people conclude it is
not in there.
1.21.0
- Every remote destination now explains itself. The Storage screen carries a
short set-up guide for Amazon S3, FTP and Google Drive — collapsed until you want
it. Each one covers the step people actually miss and names the error you get when
you miss it: the IAM policy to paste and why the wrong Region reads as a bad
signature; why a backup folder must not be web-reachable, and the login that
succeeds then hangs; and, for Drive, publishing the consent screen before pressing
Connect — plus why adding yourself as a test user works for a week and then stops.
1.20.0
Uploading to Google Drive and to Amazon S3 both worked again. One line broke
them both, and it was not the services.
- WordPress hands response headers back as a case-insensitive dictionary object, not
an array. Casting that object to an array does not give you the headers — it gives
you its internal store under a key no lookup will ever match. So Google Drive could
not find the upload session it had just been given, and S3 could not find the ETag
for a multipart part. Both failed as though the service had refused.
- When either does genuinely fail now, the message says what came back — the status
and the headers — instead of pointing at the service.
1.19.5
- A rehearsal now says how many values it skipped because they were already
unreadable when the backup was taken. Passing quietly over them would be the same
half-truth the check exists to catch.
1.19.4
- A rehearsal no longer blames the domain rewrite for data that arrived unreadable.
A value that could not be unserialized before the rewrite is skipped: restoring it
loses nothing it still had, and failing the package for it was an accusation.
- When a rehearsal does find broken data it now names the row —
option_name or
meta_key rather than only the table and column, so it can be acted on.
1.19.3
- Rehearsing a package whose tables carry foreign keys no longer reports a failure
that is not there. A rehearsal imports its copy into the same database as the live
site, and a foreign key's constraint name has to be unique across the whole
database — so the copy collided with the constraint the real table already held.
1.19.2
The first rehearsal run on a real site found a restore bug, which is the whole
reason the feature exists.
- A restore could fail on any site with foreign keys between tables. The export
switches foreign key checks off in its first line, but that is a session setting
and a restore spans many requests on a fresh connection each time. From the second
slice onward the checks were back on, so a table referencing one the dump had not
reached yet failed outright. Both the restore installer and the rehearsal now set
it per slice, and hand it back afterwards.
1.19.1
Three things that looked wired up and were not. All found by driving the plugin in a
browser rather than by reading it.
- Test connection and Test notification did nothing. The admin script gave up on
any screen without a Create Backup button — which is every screen those buttons are
on. The destination dropdown stopped switching fields for the same reason.
- Rehearse failed with a fatal error. A missing import meant the button called a
class that, from where it was calling, did not exist.
- The Backups strip said "This server only" while packages went to Google Drive.
It only ever recognised Amazon S3. FTP and Drive now say where they go.
1.19.0
Rehearsing no longer depends on somebody remembering to press the button.
- Rehearse weekly, in Settings. The newest full backup is proven every Sunday,
three hours after your backup hour, and you are told only if it fails.
- The hour is derived rather than asked for, so a rehearsal cannot be scheduled on
top of a backup — and it stands aside if it finds one running.
- A rehearsal too long for one cron request resumes where it stopped, the same way
the button and WP-CLI already did.
wp sitecarry status now reports the rehearsal schedule and its next run.
1.18.0
Rehearsing now finishes the job: proving the imported database is a WordPress that
would actually work, not merely one that imported.
- A sixth rehearsal step. The scratch database is moved onto the rehearsal's own
table prefix the way a real restore would — in the data as well as the table names
— and then read the way WordPress reads it on the way up: it knows its own address,
it has roles, and somebody still holds one. A prefix move that renames the tables
and forgets the data passes every earlier step and produces a site that loads and
locks you out. That one now fails, and says why.
- Step six reads the restored data. It does not execute WordPress.
1.17.0
Rehearsing now tests the thing that actually breaks migrations, and tells you when
it fails.
- Serialized data is put through a domain change and checked. WordPress stores
widget settings, theme options and every page-builder layout as serialized data,
where each string records its own byte length. A restore to a longer address that
does not repair those lengths produces a site that loads, looks right, and has
quietly lost its layouts. A rehearsal now rewrites the imported rows to a
deliberately longer address and confirms WordPress can still read every one of
them. Nothing is written — the rows are tested in memory and dropped.
- A rehearsal that fails now tells you, by email or webhook, on the same
failures-only setting backups use. A rehearsal that passes stays quiet: a weekly
message saying everything is still fine stops being read within a month, and the
one that mattered goes unread with it.
- The Backups screen has a Last proven line, and each package carries its own
badge: proven to restore, or rehearsal failed. A package nobody has tested says
so rather than showing nothing.
1.16.0
This release adds the thing a backup plugin should have had from the start: a way
to prove a package restores, without restoring it over anything.
- Rehearse a backup from the Backups screen, or with
wp sitecarry rehearse.
It checks the archive, decompresses every file, imports the whole database into
tables of its own, and compares the row count against what was recorded when the
backup was taken. Then it drops those tables again. Nothing on your site is
touched at any point.
- This is the answer to a question verifying could never answer. Verifying proves
the archive is intact; only importing the database proves it will import.
- A rehearsal that is interrupted resumes, and one that fails still cleans up after
itself. Scratch tables left behind by a crash are swept away within a day.
- Rehearsal covers full backups only. An incremental package is a difference, and
proving one would mean replaying its whole chain.
- Restore can now change the table prefix. Two long-standing problems came out
while building it:
- Fixed: a restore rewrote URLs across every table in the database, not only the
ones it had just written. That was harmless while restores demanded an empty
database, and would have quietly rewritten a second site sharing one.
- Fixed: the
wp-config.php inside a package kept the prefix it was taken with, so
restoring under a different prefix produced a site that loaded the installation
screen instead of itself.
- The prefix is also stored inside the data — in the roles option and in every
user's capabilities. Both now move with it, so a restored site keeps its accounts
and their roles rather than silently losing them.
1.15.0
- Deleting the plugin now clears its settings and scheduled events out of the
database. Your packages in
wp-content/uploads/sitecarry/ are deliberately left
alone — they are backups, and deleting a plugin should not throw away the only
copy of someone's site.
- Housekeeping for the WordPress.org plugin directory, none of it visible in use:
byte order marks removed from the folder guard files, a missing guard file added,
template variables renamed so they cannot shadow WordPress globals, and the readme
now documents every outside service the plugin can be configured to contact.
- The restore installer is assembled slightly differently. The files shared between
the plugin and the installer now carry the plain guard WordPress expects, and the
builder removes it as it inlines them, rather than each file carrying a guard
written to suit both places at once.
1.14.0
This release fixes ten bugs found in a full review, most of them cases where a
backup could be silently corrupt or a restore could fail — the worst kind, because
you only find out when you need the backup.
- A backup interrupted partway and resumed no longer risks a corrupt archive. The
archive, its index and the database export are each cut back to their last good
point before more is written, so nothing is ever duplicated or left in a hole.
- The database export now pages through large tables by their primary key. This is
both correct — the old method could skip or double rows on tables like term
relationships — and dramatically faster on tables with millions of rows.
- A single very large file no longer lets a second copy of the backup start on top
of the first.
- Switching the storage destination off mid-backup no longer leaves a backup
spinning forever; it stops with a clear message.
- The compression setting now actually changes the compression. "Light" is now
genuinely lighter on the processor, and "Maximum" genuinely smaller.
- Stopping a backup mid-upload now cancels the upload at the other end too.
- A restore interrupted mid-statement now resumes correctly instead of wedging.
1.13.2
- A finished backup can no longer be written back into existence as a half-done one.
Anything still holding an older copy of a job — a request that was killed and
restarted, a second driver that began before the job ended — is now ignored once
the job on disk has finished.
1.13.1
- Fixed the FTP and Google Drive settings being impossible to find. Each
destination's settings were rendered hidden and only revealed by a script, so if
that script did not run they stayed invisible with no way to reach them. The
chosen destination's settings are now shown by the page itself, and the script
only handles switching.
- The Storage screen now says that choosing a destination is what reveals its
settings, rather than showing a lone dropdown and nothing else.
1.13.0
- A Stop button for a backup that is running, or one that has hung. It appears only
while a backup is in progress, and says so plainly when that backup has stopped
making progress.
- Stopping removes the half-built package. A partial package cannot be restored
from, so leaving it would only take up room and look like a backup.
- An upload in progress is cancelled at the far end too, rather than left as an
unfinished multipart upload that storage keeps charging for.
wp sitecarry stop does the same from the command line, which is the way out if
the admin screen itself cannot be reached.
1.12.1
- Fixed background backups never starting. The request that tells the site to begin
work sent the job details in its body, and because that request is deliberately
abandoned the instant it is made, the body was often never delivered — so the
backup sat at the first step for ever, looking busy. The details now travel in the
address, which is how WordPress sends its own equivalent.
- If the server says it will carry on by itself and then does not, the open tab now
notices after a few seconds and takes over, instead of watching a bar that will
never move.
- Scanning no longer shows a percentage that falls as more folders are found. A site
with a deep folder tree used to sit at a low number looking stuck; it now reports
how many files it has found.
1.12.0
- Large sites are now genuinely practical. Sitecarry writes its own archive instead
of using PHP's zip extension: every file is appended to the end and nothing already
written is touched again. Previously each batch of files rewrote the whole archive,
so a build got slower the bigger it grew — at five gigabytes, dramatically so.
- ZIP64 throughout, so neither the archive nor any single file inside it stops at 4GB.
- A compression setting: None, Light, Normal or Maximum. Already-compressed files —
images, video, audio, fonts, archives — are stored untouched at every level,
because deflating them costs processor time and saves nothing.
- Faster, and not only because of the above: the old limit of 200 files per request
existed to avoid running out of file handles, and no longer applies.
- Building a package no longer needs PHP's zip extension at all.
1.11.0
- Two more destinations: FTP and Google Drive. Uploads to all three divide into
resumable chunks the same way, so a large package survives short execution limits
wherever it is going.
- FTP resumes at a byte offset in both directions, uploads under a temporary name so
an interrupted transfer is never mistaken for a finished archive, and offers FTP
over TLS — which the settings screen recommends, plainly, because plain FTP sends
the password and the whole database in clear.
- Google Drive uses an OAuth client of your own rather than routing authorisation
through anyone else's servers, and asks only for the scope that lets it see the
files it created itself. A restore cannot pull archives back from Drive: that
would mean putting a Google token into a file that can end up at a public URL, so
those archives are downloaded from Drive by hand instead.
- Google Cloud Storage also works through the S3 setting — point Endpoint at
https://storage.googleapis.com.
1.10.0
- Restore from storage. If an archive is no longer on the server — pruned by
retention, or because the server it lived on is gone — the installer downloads it
from S3 before restoring. This is the situation a backup exists for, and until now
there was no way back from it.
- Downloads are ranged and resumable, into a
.part file, so a large archive
survives the same short execution limits as everything else and a partial one is
never mistaken for a complete one.
- Storage keys are never written into the installer. It carries the bucket and
region; the operator types the credentials when a download is actually needed.
1.9.0
- Verify a package: checks its size and checksum against what was recorded, that the
archive still opens and is structurally sound, and that the two files a restore
cannot start without are inside it. For an incremental package it checks every
archive in the set, not just the newest.
- WP-CLI:
wp sitecarry backup, list, verify, delete, status and installer.
A backup on the command line has no execution limit and no tab to keep open, and
verify exits non-zero on failure so it can run from a script.
1.8.0
- Split the admin into four screens — Backups, Settings, Storage, Notifications —
instead of one page carrying everything.
- The Backups screen now opens with a summary: when the last backup ran, when the
next one is due, where packages are kept, and whether anyone gets told if one
fails.
- Each screen saves only its own settings, so changing storage cannot disturb the
schedule.
- Packages are labelled full or incremental, and show whether they were uploaded.
1.7.0
- Incremental backups: package only the files that changed since the last backup.
The database is still exported in full every time, so a restore never depends on
reassembling it.
- A full backup is taken automatically every so often, capping how many archives a
restore has to replay.
- Files deleted from the site are recorded, so they do not reappear on restore.
- Retention now counts complete sets. A set is deleted whole or not at all, because
removing its full backup would leave the rest unrestorable.
- The restore installer replays a whole set in order, and refuses to start while any
of its archives is missing — naming the ones it cannot find.
1.6.0
- Notifications by email and to Slack or Discord. The default is failures only,
because that is the message you need to still be reading in six months.
- A failure names the step it stopped on and quotes the error, and says plainly
that the partial package must not be relied on.
- Send a test, so notifications can be proved before they are needed.
- Every way a backup can end now runs through one place, so a job abandoned by the
watchdog reports itself exactly like one that failed outright.
1.5.0
- Send packages to Amazon S3, or anything S3-compatible — Backblaze B2, Wasabi,
DigitalOcean Spaces, MinIO — by setting a custom endpoint.
- Uploading is part of the backup job, using S3 multipart, so a large package
survives short execution limits and a resumed run continues at the next part
rather than starting the transfer again.
- Deleting a package, including by retention, removes the remote copy too.
- Test connection writes a small file and deletes it again, so it proves the upload
and delete permissions a backup actually needs.
- Credentials can be defined in wp-config.php as SITECARRY_S3_KEY and
SITECARRY_S3_SECRET, keeping them out of the database — and therefore out of the
packages the database is dumped into.
1.4.0
- Scheduled backups: hourly, twice daily, daily or weekly, at an hour you choose in
your site's own timezone.
- Retention — keep the last N packages and let older ones go, so a schedule cannot
quietly fill the disk.
- A settings screen for what goes into a package: skip transients, include tables
belonging to other applications, and your own list of paths to exclude. Manual and
scheduled backups now build packages identically.
- A scheduled run is skipped while a backup is already running, so a tight schedule
cannot stack jobs on top of each other.
1.3.0
- Backups now run in the background. Start one and close the tab — the server keeps
working, and the Sitecarry screen re-attaches to a running backup when you return.
- A watchdog restarts a backup whose background run is interrupted, picking up from
where it stopped rather than starting over.
- Sites that cannot call themselves over HTTP fall back to the previous
browser-driven mode automatically, and the screen says which one is in use.
1.2.0
- Restore onto the current site with one button, no manual upload. WordPress cannot
overwrite the files it is running from, so Sitecarry places the restore installer
in the WordPress folder and hands off to it; the restore runs outside WordPress.
- The staged installer knows which site it is on and pre-fills everything except
the database password.
- An in-place restore never deletes the stored package it was started from.
- A staged installer is flagged on every admin screen until it is used or removed,
with a one-click Remove.
1.1.0
- Restore. Every package now comes with a standalone
installer.php that rebuilds
the site on any server: unpacks the export, imports the database, extracts the
files, rewrites URLs and paths, and updates wp-config.php.
- Serialization-safe search and replace that repairs string lengths, walks
doubly-serialized values, and covers the JSON-escaped URLs page builders store.
- Per-package restore passphrase; the installer refuses to run without it and
removes itself and the archive when it finishes.
- Archive entries whose paths point outside the target folder are refused.
1.0.0
- First release: resumable full-site packaging (files + database), progress UI,
package list, authenticated download and delete.