Many site owners treat images as “just files” and store them alongside WordPress or their app on the same VPS. That’s easy and cheap at first — but it’s a single point of failure. In a recent recovery case we handled, the entire uploads
folder was deleted by attackers and there were no backups: posts were safe in the DB, but images were gone. This post explains the real costs and risks of keeping images on your server, and practical alternatives (CDN / image hosts / cloud storage) that are cheaper and safer in the long run.
1. The two costs you must compare: storage cost vs recovery cost
Direct server/storage cost
-
VPS disk pricing (example, rough): adding a few tens of GB to a cheap VPS might cost $5–$30/month depending on provider (shared/VPS/managed).
-
Managed object storage (S3, Backblaze B2, Bunny Storage) typically charges per GB/month and per request (eg. $0.005–$0.02 / GB-month plus egress).
-
CDN costs add egress/requests but improve performance.
Hidden & catastrophic costs
-
If images are stored only on the server and the uploads folder is deleted (hacks, accidental rm -rf, disk failure), recovery is hard or impossible without backups or external copies.
-
Recovery efforts (time, manual Wayback + Google-cache hunts, re-creating images, developer hours) = significant money and reputation loss.
-
Downtime and broken UX (missing images) reduces trust & SEO.
Short story: paying extra $5–$30/month for safe image hosting + CDN is almost always cheaper than paying developers to manually restore images or losing traffic and credibility.
2. Why hosted images survive hacks better than local uploads
-
Separation of concerns: when images are stored externally (S3, Cloudinary, Bunny, etc.), the web app/database contains only URLs and metadata. If the app server is compromised or wiped, the images remain available.
-
Immutable, versioned storage: many object storage/CDNs can keep versions or snapshots, making accidental deletion recoverable.
-
Global distribution: CDN caches prevent a single server failure from affecting image delivery.
-
Access controls & multi-factor protections: cloud storage providers let you lock deletion behind policies.
3. Real-world scenario: what goes wrong when uploads/
is gone
-
wp_posts
still hasattachment
entries with paths like2020/03/photo.jpg
, but the file is missing — front-end shows broken images. -
Reinstalling WordPress and plugins restores layout and functionalities, but content visuals are ruined.
-
Using Wayback Machine + Google Cache you might recover ~50–80% of images — but that’s manual, slow, and incomplete.
👉 Read the full case study here: WordPress Hacked: How I Restored My Site After Everything Was Deleted
4. Cost comparison (example estimates — replace with your provider prices)
Option | Storage cost (approx) | CDN / Egress | Recovery risk |
---|---|---|---|
Keep images on basic 20GB VPS | $5–$15 / month | Usually included small amount | High — single point of failure |
AWS S3 (20GB) + CloudFront | $1–$2 / month (storage) + egress | CloudFront costs egress requests | Low — robust but needs setup |
Backblaze B2 + Bunny CDN | ~$1 / month (storage) + low egress | Cheap CDN layer | Low — cost-effective |
Cloudinary (optimized delivery + transformations) | Free tier -> paid from ~$25/month | Bundled transformations & CDN | Low — convenient, dev-friendly |
Takeaway: pure storage on a bigger VPS may seem cheaper at tiny scale, but when you factor in egress, performance, reliability, and recovery risk, object storage + CDN wins for most production sites.
5. Best practices: how to migrate and protect images
For WordPress
-
Offload uploads to object storage/CDN
-
Plugins:
WP Offload Media
(for S3),Cloudinary
plugin,BunnyCDN
plugin, orMedia Cloud
. -
Workflow: upload → file saved to S3 (or other) → return URL stored in DB.
-
-
Use signed URLs or restricted buckets for write operations so only your app can upload/delete.
-
Enable versioning / retention on the storage bucket to avoid accidental permanent deletions.
-
Use a CDN in front of the bucket for global performance and cache resilience.
-
Keep regular backups (DB + file list / object storage manifest). Even though the images are external, backup manifests and metadata are important.
For Next.js / modern fullstack apps
-
Use S3 / Cloudinary / Imgix / Bunny for uploads. Save the returned URL in your DB (Postgres, MongoDB, etc.).
-
Serve images via next/image with an external loader or via a configured CDN domain.
-
Use server-side signed upload tokens (pre-signed S3 URLs) to avoid exposing credentials.
-
Automated optimizations: Cloudinary / Imgix give on-the-fly transforms, WebP conversion, responsive sizes — saves bandwidth & dev time.
6. Backup & recovery strategy you actually need
-
DB backups: daily (or more frequent) DB dumps stored offsite.
-
Media metadata + object manifests: export media library metadata regularly (list of filenames, paths, and external URLs).
-
Object storage lifecycle: enable versioning and point-in-time restores where possible.
-
Disaster runbook: document steps to restore DB, re-link media, and temporarily point missing images to placeholders or external hosting while you recover.
-
Automate this with scheduled jobs (backup to another bucket, copy to another region).
7. Implementation checklist (quick)
-
Decide object storage provider (S3, Backblaze B2, Bunny, Cloudinary).
-
Configure a CDN in front of storage.
-
Migrate existing media to external storage (tools or scripts).
-
Update app/WordPress to write new uploads to external storage.
-
Enable bucket versioning & restricted deletion policies.
-
Schedule and test backups & restores.
-
Monitor bandwidth & costs monthly, optimize image sizes/WEBP/Avif.
8. Final recommendation
If your site is important (traffic, brand, revenue, SEO), don’t gamble by storing your entire media library on a single VPS. Use object storage + CDN — it’s typically inexpensive compared to the downtime and recovery work you’ll pay for if images get deleted. Combine that with routine backups and retention policies and you’ll reduce the chance of ever needing to do a Wayback Machine rescue again.