โ† All guides

Offload WordPress media to Cloudflare R2 without breaking URLs

A WordPress media library grows in one direction. Every image becomes several resized copies, every year adds a few gigabytes, and eventually the site is paying application-server prices to store static files that would be nearly free anywhere else.

Offloading moves those files to object storage and serves them from there. Done carefully it is invisible. Done carelessly it is the reason someone's images 404 two years later.

Why R2 specifically

Object storage bills you for three things: storage, operations and egress. For a media library, egress is the one that surprises people โ€” every visitor who loads a page pulls bytes out of the bucket, and on most providers those bytes cost money.

Cloudflare R2 charges nothing for egress. That single difference turns a media-heavy site from an unpredictable bill into a flat storage cost. R2 also speaks the S3 API, so anything that works with S3 works with R2.

If you already have S3, DigitalOcean Spaces, Backblaze B2, Wasabi or a self-hosted MinIO, all of them work the same way. The reasoning below applies to every one of them.

What usually goes wrong

The plugin rewrites your database. Some offload tools do a search-and-replace across wp_posts to point URLs at the bucket. It works, until you remove the plugin or change buckets and discover your content is now full of URLs to somewhere that no longer exists.

The move is one-way. If the tool has no "bring it back" path, you have handed your library to a vendor and a bucket. Cancel either and you have a recovery project.

Nothing verifies the copy. Upload, then delete local, and hope. A truncated upload is a permanently lost original.

The sequence that is actually safe

A move should be four steps, in this order:

  1. Copy. Upload to the bucket. Nothing local is touched, so an interrupted run changes nothing.
  2. Verify. Checksum the remote object against the local file. If they differ, stop โ€” the original is still there.
  3. Release. Only after a byte-for-byte match, remove the local copy and record the new location.
  4. Reverse, on demand. Bring files back to the server whenever you want, byte-identical.

MediaDock Pro implements exactly this, per folder or for the whole library, and it never rewrites your database. URLs are swapped while the page renders, from a cached map, which is why removing the plugin cannot leave broken links: the content in your database always pointed at your own site.

Setting it up

The short version:

  1. Create an R2 bucket in the Cloudflare dashboard.
  2. Create an API token scoped to that bucket with Object Read & Write.
  3. Connect a custom domain (or enable the r2.dev subdomain) so files are publicly reachable.
  4. In WordPress, add the storage location, paste the endpoint and bucket, and put the keys in wp-config.php rather than the database.
  5. Test the connection, then move one folder before you move everything.

The full walkthrough with the exact constants lives in the offload documentation.

What to measure afterwards

Do you actually need this?

If your library is a few gigabytes and your host gives you plenty of disk, offloading buys you very little. It starts paying when storage is a real line item, when backups have become slow enough to skip, or when you want media served from somewhere closer to your visitors than a single origin server.

Try it on your own library

MediaDock adds folders to the WordPress Media Library, and with Pro it moves your files to Cloudflare R2 or any S3-compatible storage. Free version on WordPress.org.

See what MediaDock does ย Pricing