Technical article
Move WordPress Media from Amazon S3 to Cloudflare R2 Safely
Plan a safe WordPress media migration from Amazon S3 to Cloudflare R2 with verified cutover, resumable transfers, and source cleanup control.
Moving a WordPress media library from Amazon S3 to Cloudflare R2 is not the same as copying a folder from one bucket to another. A real WordPress site has attachment records, generated image sizes, object keys, delivery URLs, plugin state, cache layers, and users who expect old posts, product galleries, downloads, and featured images to keep working during the move.
This guide focuses on the safe migration model: keep Amazon S3 authoritative, copy media to Cloudflare R2, verify the destination, cut over only after verification, and decide separately whether source objects should be removed. It is written for WordPress site owners, developers, agencies, WooCommerce operators, and media-heavy sites that already serve media from S3 and are considering R2 as the next storage target.
Why move WordPress media from S3 to R2?
The usual reasons are operational, financial, or architectural.
Cloudflare R2 exposes an S3-compatible API, so many WordPress object-storage workflows can use familiar bucket, endpoint, access key, and secret key concepts. That does not mean R2 is a drop-in replacement for every deployment, but it does make S3-to-R2 migration a practical option for WordPress media libraries that are already organized as object storage keys.
Cost is often part of the discussion, especially for sites with meaningful outbound traffic. Avoid treating any provider as automatically cheaper. The real cost depends on storage volume, request patterns, image delivery architecture, CDN behavior, cache hit rate, and how often objects are read, written, listed, or transferred. Use the current official Amazon S3 pricing and Cloudflare R2 pricing pages for current numbers instead of relying on hardcoded examples.
Other reasons can be just as important:
- reducing dependence on a single cloud provider for media delivery
- aligning object storage with an existing Cloudflare delivery stack
- separating application hosting from media storage more clearly
- simplifying future provider migration by keeping WordPress attachment metadata portable
Whatever the reason, the migration should be designed as a data-integrity problem first and a URL switch second.
What makes a storage migration risky?
WordPress does not store a single “image” for most uploads. An attachment can include the original file plus multiple generated variants, such as thumbnails, medium images, large images, scaled originals, and custom theme or plugin sizes. A WooCommerce product image or editorial featured image may rely on several of those variants in different templates.
That creates several failure modes:
- only the original file is copied, but generated variants are missing
- object keys are copied to a different prefix than WordPress expects
- delivery URLs switch before the destination is complete
- a cache continues serving old URLs while new URLs point at missing objects
- a partial migration is mistaken for a successful one
- source objects are deleted before the destination has been verified
- an object already exists in the destination with the expected name but wrong content
The last case matters more than people expect. A destination object can have the expected key and even the expected byte size while containing different bytes. Treating existence or identical size as enough evidence can make a migration look successful until a browser, editor, or restore workflow reads the wrong file.
The safe migration model
A safer S3-to-R2 migration separates transfer, verification, cutover, and cleanup.
Amazon S3 remains authoritative
|
v
Copy each tracked media object to Cloudflare R2
|
v
Verify destination objects against what WordPress expects
|
v
Cut over delivery to the verified R2 profile
|
v
Optionally remove S3 source objects after verification
The important part is the order. The source should keep serving the site until the destination has enough evidence to be trusted. Source deletion is a separate operational decision after cutover, not a side effect of starting the copy.
In KAZCODE Universal Storage Pro, provider-to-provider migration follows that model with multiple storage profiles, per-profile credentials, destination verification, verified cutover, resumable transfer behavior, and optional source cleanup. The Free plugin remains the required base plugin, and Pro adds the multi-profile migration workflow.
Before you migrate
Before touching production media delivery, write down the current state.
- Confirm the WordPress site has a recent database backup.
- Confirm whether local files still exist under
wp-content/uploads. - Identify the current Amazon S3 bucket, region, prefix, and delivery host.
- Identify whether a CDN or custom domain sits in front of S3.
- Confirm which WordPress plugin currently owns media offload state.
- Decide whether S3 source objects should be kept after cutover.
- Create the Cloudflare R2 bucket and credentials.
- Test R2 with a small, non-critical upload before moving the whole library.
- Pick a maintenance window or low-traffic period for final verification and cutover.
If the site already has local media copies, those can be useful for rollback and manual inspection. If the site relies entirely on S3 for the binary files, be more conservative: keep S3 authoritative until R2 has been verified and the frontend has been checked.
Configure Cloudflare R2
At a high level, a WordPress storage profile for Cloudflare R2 needs:
- the R2 bucket name
- the account-specific S3-compatible endpoint
- an Access Key ID
- a Secret Access Key
- the expected region behavior, commonly
autofor R2 - path-style behavior if required by the provider setup
- a public delivery hostname or custom domain if the site will serve public media from R2
Do not paste real credentials into documentation, screenshots, tickets, or shared chat threads. Use scoped credentials for the bucket where possible, and rotate credentials if they have been exposed.
The implementation details are covered in the Cloudflare R2 provider docs. If you are still planning the basic offload setup, the Cloudflare R2 WordPress media offload guide walks through the earlier decision points.
Migrating with KAZCODE Universal Storage Pro
KAZCODE Universal Storage Pro is designed for this kind of provider-to-provider move. The Free plugin provides the base WordPress media offload, restore, verification, and repair foundation; Pro adds multiple storage profiles and cross-provider migration.
The exact screen wording may evolve, but the operational workflow should stay familiar: keep the existing Amazon S3 profile, add R2 as another profile, test both sides, migrate, verify, cut over, and only then decide what to do with the source.
1. Keep your Amazon S3 profile
Do not replace the current S3 profile at the start of the migration. It is the source of the existing media objects and should continue serving the live site while R2 is being prepared.
Check that the S3 profile still has valid credentials and can read the objects currently referenced by WordPress. If credentials have been rotated or limited since the original offload setup, fix that before adding another provider.
2. Add Cloudflare R2 as another storage profile
Create a separate Cloudflare R2 profile with its own endpoint, bucket, credentials, and delivery hostname. Keeping credentials per profile avoids mixing S3 and R2 access and makes rollback easier to reason about.
This is a Pro workflow because WordPress needs to know both the source profile and the destination profile at the same time. Reusing a single profile by editing its endpoint is risky: it can make the site generate new URLs before the destination contains the files.
3. Test both provider connections
Test the S3 source profile and the R2 destination profile before migration. A good test should prove that WordPress can authenticate, reach the endpoint, and perform the required storage operations for that profile.
Do not skip the source test just because the site appears to load. A CDN or browser cache can hide broken source credentials until the migration tries to read objects directly.
4. Start migration
Start with a batch size that matches your hosting, object sizes, and timeout behavior. Media libraries with many generated variants create more object operations than their attachment count suggests. Large originals may use multipart transfer behavior so the operation can continue more reliably across provider and network limits.
The site should keep serving from S3 while objects are copied to R2. That means a failed or interrupted transfer should not immediately become a frontend outage.
5. Verify destination
Destination verification is the gate. It should confirm that the object in R2 is the object WordPress expects, not merely that something with the same key exists.
KAZCODE migration testing has covered real AWS S3 and Cloudflare R2 workflows, separate AWS and R2 credentials, interrupted multipart transfers, wrong pre-existing destination objects, and the case where a destination object has the same byte size but different content. That testing is not an industry benchmark, but it is the kind of edge-case work that a migration tool needs before it should be trusted with production media.
6. Cut over
Cutover should happen only after the destination profile is verified. In practice, that means delivery changes from the S3-backed profile to the R2-backed profile after the migration has enough evidence that the destination can serve the expected media.
After cutover, clear any relevant page, object, CDN, and browser-facing caches according to your stack. Then test both admin and frontend views before declaring the migration operationally complete.
7. Decide whether to keep or delete S3 source objects
Keeping S3 for a while after the first cutover is usually the conservative choice. It preserves a rollback path while you inspect real pages, product galleries, archives, feeds, and admin workflows.
If cost, compliance, or storage policy requires cleanup, use verified source cleanup rather than manual bulk deletion. KAZCODE Universal Storage Pro supports optional source deletion after destination verification. That option should be treated as a post-verification cleanup step, not part of proving the migration.
Why destination verification matters
Object storage APIs can tell you that an object exists. They may also tell you the byte size or metadata. Those facts are useful, but they are not always enough to prove the destination contains the correct media.
Consider a failed first attempt that created an object at the right key, followed by a manual upload or stale test object that happens to have the same size. If a migration system trusts key plus size alone, it can skip the real copy and later switch WordPress to serve incorrect content.
A safer verifier treats the destination as untrusted until it has stronger evidence. The exact evidence can depend on what the source, destination, and stored inventory provide, but the principle is simple: do not cut over based on a weak signal when the source is still available for comparison.
Temporary provider errors also need careful handling. A timeout, throttling response, authentication failure, or provider outage should not be converted into “missing” without enough evidence. Unknown is a different state from missing. That distinction prevents repair or cleanup tooling from making destructive decisions from incomplete information.
What happens if migration is interrupted?
Interrupted migrations are normal engineering events. PHP processes time out, queues get paused, providers throttle, credentials expire, deployments happen, and network links drop.
The migration design should assume interruption and make resume safe:
- completed attachment/object work should be recorded
- incomplete work should be retryable
- multipart transfers should not require the whole media library to restart
- source delivery should remain active while the destination is incomplete
- verification should be repeatable after the interruption is resolved
Avoid migration plans that depend on one long uninterrupted copy job and immediate source deletion. They are fragile even when the happy path works during a small test.
Should you delete the original S3 objects?
For the initial migration, keep the original S3 objects unless you have a strong reason not to. Storage cost for a short overlap period is usually easier to manage than losing a rollback path.
After R2 verification and a period of production observation, decide based on your site:
- Keep S3 if you want an immediate rollback source.
- Keep S3 if compliance or retention policy requires another copy.
- Delete S3 if verified cleanup aligns with cost, data retention, and rollback requirements.
- Never delete S3 manually until you understand whether all originals and generated variants have been copied and verified.
If you choose cleanup, prefer a workflow that only removes source objects after destination verification. That is the difference between intentional cleanup and hoping that a bulk-delete command was aimed at the right data.
How to verify the migration yourself
Automated verification is necessary, but it should not be the only check before you walk away from a production migration.
Use a manual checklist:
- Open recent and old Media Library items in wp-admin.
- Check originals and generated thumbnail sizes.
- Test a few large uploads if your site uses PDFs, videos, ZIP files, or high-resolution images.
- Open pages, posts, product galleries, and archive views on the frontend.
- Inspect image URLs and confirm they use the intended R2 delivery hostname after cutover.
- Confirm the expected objects exist in the R2 bucket.
- Review plugin logs or migration reports for failed, skipped, unknown, or retried items.
- Restore one non-critical media item if restore is part of your rollback plan.
- Keep S3 available until real traffic has exercised the important templates.
For product sites, include cart, checkout-adjacent product images, feeds, structured data, and any image transformation layer in the review. A migration that works for blog posts but misses WooCommerce gallery sizes is not complete.
S3 to R2 migration checklist
Use this as the condensed runbook:
- Back up the WordPress database.
- Record the current S3 bucket, region, prefix, credentials model, and delivery host.
- Confirm the existing S3 profile can still read source objects.
- Create the R2 bucket and scoped credentials.
- Configure and test the R2 destination profile.
- Keep S3 authoritative during transfer.
- Migrate in resumable batches.
- Verify destination objects.
- Cut over delivery only after verification.
- Clear relevant caches.
- Manually inspect admin, frontend, generated sizes, and large files.
- Keep S3 for a rollback window.
- Use verified source cleanup only when you are ready to remove S3 objects.
FAQ
Will WordPress media URLs break?
They should not break if cutover happens after destination verification and delivery URLs are generated from the active storage profile. URLs are most likely to break when the destination is incomplete, object keys change unexpectedly, or delivery switches before verification.
Do I need to download files to my WordPress server first?
Not for a provider-to-provider migration workflow. The point is to move tracked objects from the S3 source profile to the R2 destination profile without turning the WordPress server into a manual staging disk. Local restore is still useful as a recovery capability, but it is not the normal migration path.
What happens to generated thumbnails?
Generated image sizes need to be treated as first-class media objects. A safe migration copies and verifies the original plus generated variants that WordPress expects. Do not judge success by checking only the original upload.
Can I keep S3 as a backup?
Yes. For many sites, keeping S3 for a defined rollback window is the safest operational choice. Just be clear whether S3 is a temporary fallback, a retained backup copy, or a source that should eventually be cleaned up.
What if R2 already contains a file with the same name?
The destination object should not be trusted simply because the key exists. A safe migration verifies that the existing destination object matches what WordPress expects. Wrong pre-existing destination objects are one of the reasons verification must be more than an existence check.
Can large files resume after failure?
Large media objects should be handled with resumable and multipart-aware transfer behavior where the provider and migration workflow support it. The important operational property is that an interruption should not force the whole library to start over or make S3 stop serving before R2 is ready.
Can I migrate back from R2 to S3?
Conceptually yes, provider migration is a source-to-destination problem. In practice, rollback is easiest when you keep the original S3 profile and source objects available until the R2 cutover has been verified in production.
Need a safer WordPress S3 to R2 migration? KAZCODE Universal Storage Pro can migrate between storage profiles while keeping the source authoritative until the destination is verified. Start with the Universal Storage overview or review the S3 to R2 migration docs.