Offload WordPress Media to S3-Compatible Storage
Your WordPress media library grows quietly until the day it doesn't — a disk quota warning shows up in cPanel, backups start timing out, or a client asks why the product gallery upload just failed. Moving your images and videos to S3-compatible object storage fixes the root cause instead of buying you another few months on a bigger plan.
The symptom: your hosting account is quietly filling up with images
This usually shows up in one of a few ways:
- cPanel's disk usage meter creeps past 80-90%, and you keep deleting old backups to buy space.
- A WooCommerce or gallery-heavy site adds hundreds of megabytes a month just from product photos, each one saved in 3-5 resized copies.
- JetBackup or your VPS backup job starts taking noticeably longer, because it's re-copying the same media folder every night.
- You hit an inode limit before you hit a storage limit — thousands of small thumbnail files count against you even if their total size is tiny.
None of this is a bug. It's just what happens when every uploaded image lives on the same disk as your PHP files, your database, and your logs.
The cause: WordPress was never designed to separate "app" from "storage"
By default, wp-content/uploads/ sits on the same filesystem as everything else. WordPress generates several sizes for every image (thumbnail, medium, large, and whatever your theme registers), and none of it ever gets cleaned up automatically — even after you delete the original from the media library in some edge cases with orphaned files.
On shared cPanel hosting this eats into your disk quota. On a VPS it eats into root disk space that your OS, logs, and MySQL data also depend on. Either way, the fix is the same: stop storing media on the server that runs your site, and start storing it in object storage designed for exactly this.
Picking a storage backend
"S3-compatible" means you're not locked into AWS. Several providers speak the same API, and pricing varies a lot on egress (the fee for serving files out to visitors), which matters more than the storage cost itself for a media-heavy site.
| Provider | Free egress? | Good for |
|---|---|---|
| AWS S3 | No (paid per GB out) | Sites already using other AWS services |
| Cloudflare R2 | Yes, zero egress fees | Most WordPress sites — cheapest at scale |
| DigitalOcean Spaces | 250GB free transfer/month | Simple pricing, built-in CDN |
| Backblaze B2 | Free egress via Cloudflare | Budget backups + media combined |
| Wasabi | No fees if usage stays reasonable | Flat-rate storage, no surprises |
If you're not sure, Cloudflare R2 is the easiest default for most Getwebup customers — no egress bill, and it plugs into a CDN you're probably using anyway.
Setting it up
1. Create the bucket and access keys
Create a bucket (call it something like yoursite-media) and generate an access key/secret pair scoped to that bucket only — don't reuse account-wide root keys. Set the bucket to allow public reads for objects, since your images need to be publicly viewable.
Add a CORS policy so browsers don't block requests to the bucket from your domain:
[
{
"AllowedOrigins": ["https://yourdomain.com"],
"AllowedMethods": ["GET"],
"AllowedHeaders": ["*"],
"MaxAgeSeconds": 3600
}
]
2. Install the offload plugin
WP Offload Media Lite (free) or its Pro version handles the heavy lifting — it hooks into wp_generate_attachment_metadata and uploads each new file to your bucket automatically, then rewrites the URL WordPress stores in the database. Install it, go to Settings, and enter:
- Your access key and secret
- The bucket name and region (or endpoint URL, for non-AWS providers)
- Whether to keep a local copy or remove files from the server after upload
For most cPanel and small VPS setups, keep local copies off once you've confirmed everything works — that's the whole point of doing this.
3. Test with a real upload
Upload a new image through the media library, then check its URL in the browser — it should point to your bucket or CDN domain, not your site's own domain. If it 403s, your bucket's public-read policy or CORS setup is the usual culprit; double-check the bucket policy allows s3:GetObject for anonymous requests on that prefix.
4. Migrate your existing library
Most offload plugins include a bulk migration tool that walks your existing uploads folder, pushes each file to the bucket, and rewrites the database references. Run it during low-traffic hours — on a library with tens of thousands of images this can take a while and will use CPU/bandwidth the whole time.
Take a database backup first. The migration rewrites URLs across wp_posts and postmeta; if it's interrupted halfway, you want an easy way back.
5. Put a CDN in front (recommended, not optional)
Object storage on its own is fine, but a CDN in front of the bucket cuts latency for visitors far from the bucket's region and reduces repeated GET requests. If you're already using Cloudflare for your domain, this is usually just pointing a CNAME at your bucket's public endpoint and proxying it orange-cloud.
Common pitfalls
| Problem | Cause | Fix |
|---|---|---|
| Images broken after migration | Bucket policy not public, or wrong region/endpoint saved | Re-check bucket policy and plugin settings, re-run "verify" in the plugin |
| Mixed content warnings | Bucket URL serving over http instead of https | Force https in the plugin's URL settings, or use the CDN domain instead of the raw bucket URL |
| New uploads fail with a timeout | Large files + slow PHP execution limits | Raise max_execution_time in MultiPHP INI Editor, or upload in smaller batches |
| Old cached pages still show local image URLs | Page cache or CDN cache serving stale HTML | Purge WordPress and CDN cache after migration finishes |
Prevention: keep it from filling up again
- Set a lifecycle rule on the bucket to auto-delete truly unused image sizes if your theme changes and stops using them.
- Compress images on upload with a plugin like ShortPixel or Imagify — smaller files mean lower storage and egress costs even after offloading.
- Keep a lightweight local backup schedule (via cPanel Backup Wizard or JetBackup) for your database and plugin files now that the media bulk is off-server — backups finish faster and cost less to store.
- Check your cPanel disk usage or VPS
df -hmonthly. Offloading media buys you headroom, not immunity — logs, caches, and database growth still need watching.
If you'd rather skip the DIY setup, Getwebup's support team can configure object storage and CDN offload as part of a WordPress hosting migration — worth mentioning if disk space keeps coming up as a recurring support ticket.
Frequently asked questions
Will offloading media break my existing image URLs?
Not if you use a plugin like WP Offload Media — it rewrites the URLs stored in your database to point to the bucket or CDN, so old links keep working. Always take a database backup before running the bulk migration, since it touches wp_posts and postmeta.
Do I still need local backups if my media is on S3-compatible storage?
Yes, but they'll be smaller and faster. Your bucket provider handles redundancy for the media itself, but you still want database and plugin backups through cPanel's Backup Wizard or JetBackup — object storage isn't a replacement for a full site backup.
Which S3-compatible provider is cheapest for a typical WordPress site?
Cloudflare R2 usually wins on total cost because it charges zero egress fees — you only pay for storage. AWS S3 and DigitalOcean Spaces are solid too, but egress charges add up faster on media-heavy or high-traffic sites.
Can I offload media on shared cPanel hosting, or only on a VPS?
Both. The offload plugin runs inside WordPress itself, so it works the same way regardless of whether you're on shared cPanel hosting or a VPS — it's really about your hosting disk quota, not your server type.