In this guide
  1. Why self-host an image CDN
  2. The core architecture
  3. On-the-fly resizing and format conversion
  4. Caching strategy
  5. Storage backend choices
  6. Multi-tenant considerations
  7. When a managed service still makes sense
  8. Frequently asked questions

Managed image CDN services are genuinely convenient, but their per-image or per-bandwidth pricing scales linearly with usage in a way that can get expensive once you're serving meaningful traffic. A self-hosted image pipeline — origin storage, on-the-fly transforms, and a caching layer — is a well-understood architecture you can run on a single VPS or a small cluster, at a fraction of the recurring cost.

Why self-host an image CDN

Three reasons founders typically make this switch: cost at scale (bandwidth and per-transform pricing from managed services adds up fast for image-heavy products), control (you decide exactly which transforms, formats, and cache rules apply, rather than working within a vendor's feature set), and data residency (some products need images to never leave infrastructure you control).

The core architecture

The standard pattern has three layers: an origin store holding the original uploaded images (object storage, not local disk — see our single-VPS SaaS guide on why), a transform service that resizes/crops/converts images on request, and a caching layer (Nginx's own proxy cache, or a dedicated cache like Varnish) so the transform only runs once per unique combination of image and parameters, not on every single request.

On-the-fly resizing and format conversion

Open-source tools handle this layer well without building anything from scratch — imgproxy and thumbor are both mature, self-hostable options that accept a URL pattern encoding the desired width, height, crop mode, and output format, then fetch the source image and perform the transform on demand. Both support modern formats (WebP, AVIF) with automatic negotiation based on the requesting browser's Accept header, which meaningfully reduces bandwidth compared to serving JPEG or PNG unconditionally.

Caching strategy

Cache transformed images aggressively and for a long time — transformed output for a given source image and parameter set never changes unless the source image itself changes, so a cache lifetime measured in months (with a cache-busting strategy on the URL when an image is genuinely replaced) is appropriate. Nginx's proxy_cache directive in front of your transform service handles this well for a single-server setup; a dedicated CDN in front of that (even a budget one) adds edge caching closer to your users for very little additional cost.

Storage backend choices

Any S3-compatible object storage works as the origin — this is a solved problem and there's no need to build custom storage logic. What matters more is your key/path naming convention: structure paths so a given tenant's or user's images are grouped predictably (for example, by tenant ID and upload date), which makes future operations like per-tenant export, deletion, or migration far simpler than a flat, unstructured bucket.

Multi-tenant considerations

If you're running this for a multi-tenant SaaS (see our multi-tenant architecture guide), namespace both storage paths and transform-service access by tenant, and consider per-tenant rate limits on the transform endpoint specifically — image resizing is CPU-intensive, and it's the kind of endpoint a single misbehaving tenant (or a scraper) can hammer disproportionately compared to the rest of your application.

When a managed service still makes sense

If your image volume is genuinely small, or your team doesn't have the bandwidth to own another piece of infrastructure, a managed service remains the right pragmatic choice — the engineering time saved is real, and self-hosting is worth doing when the cost curve or control requirements actually justify it, not by default.

Frequently asked questions

Is imgproxy or thumbor better for a new project?

imgproxy tends to be favored for raw performance and a smaller resource footprint, written in Go; thumbor has a longer track record and a larger plugin ecosystem for less common transforms. For most standard resize/crop/format-conversion needs, either handles the job well — the choice matters less than getting caching and storage right around it.

How much does self-hosting an image CDN actually save versus a managed service?

It depends heavily on your image volume and bandwidth — at low volume, a managed service's free or cheap tier may cost less than the engineering time to self-host. The savings become significant once you're serving enough volume that per-image or per-bandwidth pricing on a managed service adds up to more than the cost of a dedicated VPS running the equivalent open-source stack.

Do I still need a CDN in front of a self-hosted image pipeline?

Yes, if your users are geographically distributed — a CDN caches transformed images at edge locations close to each user, reducing latency beyond what a single-origin server (even with good caching) can achieve. Budget CDN options paired with a self-hosted origin are a common and cost-effective combination.