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

  1. Back up the database and wp-content/uploads.
  2. Confirm single-site WordPress (multisite unsupported).
  3. Ensure object storage quota and network path from the app server to the S3 API.
  4. Install WP to S3 (Installation).

Steps

  1. Install and activate WP to S3 (release zip with vendor/, or source + composer install --no-dev).

  2. Open Media → WP to S3 → Settings.

  3. Configure the connection with a provider preset + bucket/keys.
    Keep Delete local after upload off for the entire first migrate pass.

  4. Click Test and ensure PUT, GET, and DELETE all pass. Fix auth/endpoint issues now — not mid-migrate.

  5. Save Changes, then Enable the plugin and save again.
    From this moment, new Media Library uploads offload live while locals remain (delete-local off).

  6. Pilot migrate (recommended):

    • Admin: Migrate tab → set path filter e.g. 2024/01 → Start, or
    • CLI: wp s3 migrate --path=2024/01 --dry-run then wp s3 migrate --path=2024/01 --resume
      Confirm a sample of files in the bucket and that the site still serves media.
  7. Full migrate:

    wp s3 migrate --dry-run
    wp s3 migrate --resume
    

    Or 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=auto default when installed).
  8. Short freeze window (optional but good hygiene):

    • Pause editorial uploads or use maintenance mode.
    • Re-run wp s3 migrate --resume to catch stragglers created during the long first pass.
  9. Verify:

    wp s3 verify --sample=500
    wp s3 verify
    

    Investigate any missing/mismatch rows before deleting locals.

  10. Optional: free disk only after verify is clean:

    wp s3 migrate --resume --delete-local
    

    Or 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).

  11. 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.

  12. 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 pathObject key (prefix empty)Public URL (default)
wp-content/uploads/2020/03/foo.jpg2020/03/foo.jpghttps://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)

ClassBehavior
Auth / config (401, 403, bad keys, NoSuchBucket)Halt job; fix config and resume
Unreadable / missing local fileSkip + log; continue
429 / 503 / transient 5xxBackoff + bounded retry
Other put failuresSkip + 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