Migration reference
Operator cutover sequence, CLI flags, and error policy for bulk migrate.
Target scenario: existing library under wp-content/uploads moving to S3-compatible storage with minimal downtime. Live offload stays on while backfill runs (no migrate-only mode).
Prerequisites
- Plugin installed (release zip with vendor, or
composer install --no-dev) wp s3 doctoror admin Test connection passes- Enough object storage quota and a stable network path from the app server
- Backups of the database and uploads volume (standard hygiene)
- Multisite is not supported
Key layout reminder
Local wp-content/uploads/2020/03/foo.jpg → object key 2020/03/foo.jpg (plus optional prefix).
Default public URL stays https://yoursite.com/wp-content/uploads/2020/03/foo.jpg.
Recommended cutover sequence
1. Configure without deleting local files
define('WP_TO_S3_ENABLED', true);
define('WP_TO_S3_DELETE_LOCAL', false); // keep locals until verified
// … endpoint, region, bucket, keys …
Or use Media → WP to S3 with a provider preset. Enable the plugin so new uploads offload live.
2. Dry run (CLI) or start carefully (admin)
wp s3 migrate --dry-run
Admin: Media → WP to S3 → Migrate → Start (progress UI; use path filter for a pilot year first).
3. Bulk migrate (first full pass)
WP-CLI (prefer for large libraries; auto-uses s5cmd when installed):
wp s3 migrate --resume
Admin (shared hosting): Start migrate; leave the tab open or rely on WP-Cron; use Run next batch if cron is disabled.
- Walks the uploads tree
- Resume: skips objects where remote size already matches local
- Safe to re-run after interruptions
- PHP engine default concurrency 3 (
--concurrency=1or admin “sequential safe mode” for fragile hosts)
For very large trees, migrate by year/month:
wp s3 migrate --path=2018 --resume
wp s3 migrate --path=2019 --resume
4. Freeze window (short)
- Put the site in maintenance mode or briefly pause editorial uploads
- Re-run migrate to catch files created during the long first pass
- Verify:
wp s3 verify --sample=500
wp s3 verify
5. Free disk (optional)
With locals still present, Serve does nothing (local file wins). When ready:
wp s3 migrate --resume --delete-local
Or use Media Library bulk Delete local (if remote OK) / attachment metabox.
6. CDN (optional)
See same-host-vs-cdn.md. Content rewrite of historic HTML is default off — dry-run under Tools first.
Error policy
| Class | Behavior |
|---|---|
| Auth / config (401, 403, bad keys, NoSuchBucket) | Halt job; clear admin message |
| Unreadable / missing local file | Skip + error row; continue |
| 429 / 503 / transient 5xx | Backoff + bounded retry |
| Other put failures | Skip + log |
Admin progress screen offers a downloadable report.
Commands reference
wp s3 doctor
wp s3 migrate --engine=auto --numworkers=256
wp s3 migrate --engine=s5cmd --numworkers=256 --concurrency=5
wp s3 migrate --engine=php --concurrency=3
wp s3 migrate [--dry-run] [--path=<subdir>] [--resume] [--no-resume]
wp s3 migrate [--delete-local] [--keep-local]
wp s3 verify [--sample=<n>] [--path=<subdir>]
Parallel uploads with s5cmd
Bulk CLI migrate defaults to s5cmd when the binary is on PATH (or WP_TO_S3_S5CMD_PATH). Single-file Media Library uploads and admin job batches use the PHP SDK.
# optional
define('WP_TO_S3_S5CMD_PATH', '/usr/local/bin/s5cmd');