If your VPS disk usage graph only ever goes up, there's a good chance wp-content/uploads is the culprit. Every product photo, blog image, and PDF a customer uploads sits on local disk forever by default — and on a busy WooCommerce store or media-heavy blog, that folder can quietly grow into gigabytes a month. Moving that media to S3-compatible object storage gets it off your VPS for good, without touching the rest of your WordPress install.

Symptom: Disk Usage Climbs and Nothing You Delete Helps

This usually shows up as one or more of the following:

  • df -h shows /home or /var creeping toward 90%+ even though you haven't installed anything new
  • du -sh wp-content/uploads/* reveals years of full-size originals, thumbnails, and WooCommerce product galleries nobody ever cleans up
  • Backups take longer every week because the backup tool has to copy the same growing uploads folder each time
  • You resize the VPS disk once, and six months later you're right back where you started

Deleting old posts or optimizing images helps a little, but it's a losing race if new uploads keep landing on the same disk that runs your database, PHP, and everything else.

Cause: WordPress Assumes Local Disk Is Free and Infinite

By default, WordPress writes every upload straight into wp-content/uploads/YYYY/MM/ on the server's local filesystem, and it generates multiple resized copies of every image (thumbnail, medium, large, and whatever sizes your theme registers) — so one 3MB photo upload can easily turn into 8-10MB once WordPress finishes generating variants. None of that is designed to be deleted, and nothing in WordPress core tells you when the folder is getting large. On a VPS where the disk also holds your OS, MySQL data, and logs, that uploads folder competes directly with things that actually need the space to keep the site running.

The fix isn't to stop uploading images — it's to stop storing them on the same disk that runs the server.

Fix: Move Uploads to S3-Compatible Object Storage

Step 1 — Pick a Storage Provider

You don't need Amazon S3 itself. Any S3-compatible provider works, and a few are considerably cheaper for pure storage + egress:

ProviderGood For
Backblaze B2Cheapest raw storage, free egress via Cloudflare
WasabiNo egress fees, flat monthly pricing
Amazon S3Widest plugin compatibility, more expensive at scale
Self-hosted MinIOIf you already run a second VPS and want full control

Create a bucket, note the endpoint URL, and generate an access key + secret key from the provider's dashboard. Keep the bucket private — the plugin will serve files through signed or public read-only URLs, not by opening the whole bucket to the internet.

Step 2 — Install an Offload Plugin

WP Offload Media (the Lite version is free and covers Backblaze B2, DigitalOcean Spaces, and generic S3-compatible endpoints) is the most reliable option. Install it from Plugins > Add New, then go to Settings > Offload Media and enter:

Access Key ID: your-key
Secret Access Key: your-secret
Bucket Name: your-bucket
Region/Endpoint: your-provider-endpoint

Save, then upload a test image through Media > Add New. If it's configured correctly, the image lands in your bucket and the Media Library shows a small cloud icon confirming it's offloaded.

Step 3 — Migrate Existing Uploads

New uploads going forward is the easy part — the years of existing files in wp-content/uploads need a one-time push to the bucket. WP Offload Media's paid tier includes a bulk offload tool that does this from the dashboard with a progress bar. On the free tier, you can do the same thing manually with the AWS CLI (it works against any S3-compatible endpoint):

aws s3 sync /home/username/public_html/wp-content/uploads/ \
  s3://your-bucket/wp-content/uploads/ \
  --endpoint-url https://your-provider-endpoint

Run this once to copy everything, verify a handful of URLs load correctly in the browser, then run it a second time with --delete removed still off, just to confirm nothing changed since the first sync before you touch local files.

Step 4 — Decide Whether to Remove Local Copies

Most offload plugins default to keeping a local copy after uploading to the bucket, which doesn't solve your disk problem at all. In WP Offload Media, under Settings, there's an option to remove files from the server after a successful offload. Turn that on only after you've confirmed the bucket sync is complete and a few random images load fine on the live site — this step is what actually frees the disk space.

Step 5 — Point a CDN or CNAME at the Bucket (Optional but Recommended)

Serving images directly from your-bucket.s3.provider.com works, but a custom domain (e.g. media.yourdomain.com) looks cleaner and lets you swap providers later without breaking every image URL on the site. Add a CNAME record in your DNS zone pointing at the provider's endpoint, then set that custom domain in the plugin's settings before you run the bulk offload — changing it afterward means re-writing every URL already stored in the database.

Prevention: Keep New Uploads From Piling Up Again

HabitWhy It Matters
Check du -sh wp-content/uploads monthly, even after offloadingConfirms the "remove local copy" setting is actually working
Disable unused intermediate image sizes with a plugin like Perfmatters or a theme filterStops WordPress generating 5-6 resized copies you'll never use
Set a lifecycle rule in the bucket to move old files to cold storageCuts monthly storage cost on archives nobody views anymore
Test one upload after any plugin or theme updateCatches silent offload failures before the uploads folder quietly refills

Object storage bills are usually a few dollars a month for a typical site — far cheaper than resizing your VPS disk every time uploads catch up with you again.

Frequently Asked Questions

Will offloading media break existing image URLs in old posts?

No, if you use a plugin like WP Offload Media. It rewrites the URLs stored in the database to point at the bucket (or your custom CDN domain) automatically, so existing posts and pages keep working without manual find-and-replace.

Do I still need local backups of my WordPress site if media is on S3?

Yes. Object storage protects against your VPS disk filling up, not against accidental deletion or a bad plugin update. Keep your regular backup routine running — it'll just finish faster since the uploads folder is no longer the biggest part of it.

Can I use this same setup for WooCommerce product images?

Yes, WooCommerce images are stored the same way as any other WordPress media, so they offload identically. Just double-check your product gallery displays correctly after the bulk migration, since some themes cache resized image URLs separately.

What happens if the offload plugin fails partway through a bulk migration?

Nothing already offloaded is lost — most tools track progress and can resume where they left off. Don't enable "remove local copy" until you've confirmed the full migration finished and spot-checked several images, so you always have a local fallback if something needs re-running.

Is this worth it on a small site with only a few hundred images?

Probably not urgently, but it's worth setting up before it becomes a problem rather than after. If your VPS disk is already comfortable, you can skip the local-copy removal step for now and just keep new uploads mirrored to the bucket as a safety net.