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

  1. Plugin installed (release zip with vendor, or composer install --no-dev)
  2. wp s3 doctor or admin Test connection passes
  3. Enough object storage quota and a stable network path from the app server
  4. Backups of the database and uploads volume (standard hygiene)
  5. 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.

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.

For very large trees, migrate by year/month:

wp s3 migrate --path=2018 --resume
wp s3 migrate --path=2019 --resume

4. Freeze window (short)

  1. Put the site in maintenance mode or briefly pause editorial uploads
  2. Re-run migrate to catch files created during the long first pass
  3. 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

ClassBehavior
Auth / config (401, 403, bad keys, NoSuchBucket)Halt job; clear admin message
Unreadable / missing local fileSkip + error row; continue
429 / 503 / transient 5xxBackoff + bounded retry
Other put failuresSkip + 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');