Existing WordPress site (migrate)
Step-by-step: configure and test S3, enable live offload, migrate existing uploads, verify, optional delete-local cutover.
Use this guide when the site already has media under wp-content/uploads and you want those objects in the bucket and new uploads to offload live.
Product rule: there is no migrate-only mode. You enable live offload, then backfill existing files while new uploads continue to work.
Before you start
- Back up the database and
wp-content/uploads. - Confirm single-site WordPress (multisite unsupported).
- Ensure object storage quota and network path from the app server to the S3 API.
- Install WP to S3 (Installation).
Steps
Install and activate WP to S3 (release zip with
vendor/, or source +composer install --no-dev).Open Media → WP to S3 → Settings.
Configure the connection with a provider preset + bucket/keys.
Keep Delete local after upload off for the entire first migrate pass.Click Test and ensure PUT, GET, and DELETE all pass. Fix auth/endpoint issues now — not mid-migrate.
Save Changes, then Enable the plugin and save again.
From this moment, new Media Library uploads offload live while locals remain (delete-local off).Pilot migrate (recommended):
- Admin: Migrate tab → set path filter e.g.
2024/01→ Start, or - CLI:
wp s3 migrate --path=2024/01 --dry-runthenwp s3 migrate --path=2024/01 --resume
Confirm a sample of files in the bucket and that the site still serves media.
- Admin: Migrate tab → set path filter e.g.
Full migrate:
wp s3 migrate --dry-run wp s3 migrate --resumeOr admin Media → WP to S3 → Migrate → Start (leave the tab open or use Run next batch if WP-Cron is unreliable).
- Resume skips objects whose remote size already matches local.
- Safe to re-run after interruptions.
- Large libraries: migrate by year (
--path=2018,--path=2019, …) or use s5cmd via CLI (--engine=autodefault when installed).
Short freeze window (optional but good hygiene):
- Pause editorial uploads or use maintenance mode.
- Re-run
wp s3 migrate --resumeto catch stragglers created during the long first pass.
Verify:
wp s3 verify --sample=500 wp s3 verifyInvestigate any missing/mismatch rows before deleting locals.
Optional: free disk only after verify is clean:
wp s3 migrate --resume --delete-localOr Media Library bulk Delete local (if remote OK) / attachment metabox.
Same-host URLs continue to work: the plugin streams from S3 when the local file is gone (X-WP-To-S3: 1).Optional: CDN / public base URL after offload is stable — see Same-host vs CDN.
Historic post HTML rewrite is default off; dry-run under Tools before apply.Steady state: leave Enabled on so all future uploads offload. Re-run migrate/verify only if you re-introduce large local trees.
Key layout reminder
| Local path | Object key (prefix empty) | Public URL (default) |
|---|---|---|
wp-content/uploads/2020/03/foo.jpg | 2020/03/foo.jpg | https://yoursite.com/wp-content/uploads/2020/03/foo.jpg |
Optional prefix media → key media/2020/03/foo.jpg (same-host URL unchanged unless public base URL is set).
Error policy (migrate)
| Class | Behavior |
|---|---|
| Auth / config (401, 403, bad keys, NoSuchBucket) | Halt job; fix config and resume |
| Unreadable / missing local file | Skip + log; continue |
| 429 / 503 / transient 5xx | Backoff + bounded retry |
| Other put failures | Skip + log |
Admin progress includes a downloadable report.
Commands reference
wp s3 doctor
wp s3 migrate --dry-run
wp s3 migrate --resume
wp s3 migrate --path=2019 --resume
wp s3 migrate --engine=php --concurrency=3
wp s3 migrate --engine=s5cmd --numworkers=128
wp s3 migrate --resume --delete-local
wp s3 verify --sample=500
wp s3 verify
Related
- Migration notes (operators) — condensed cutover reference
- Configuration — constants and presets
- Usage — admin UI and Media Library tools
- New site guide — if you do not need migrate