Building a Real-Time Image Optimization CDN with Imgproxy and Caddy Server on a VPS
Introduction: The Cost of Unoptimized Images in Modern Web Development
In the modern digital landscape, visual content is paramount to user engagement. However, high-resolution images are often the primary culprit behind sluggish page load times, poor user experiences, and inflated bandwidth bills. According to web performance standards, unoptimized images directly degrade your Core Web Vitals—specifically Largest Contentful Paint (LCP)—which negatively impacts your SEO rankings.
While third-party Image CDNs offer seamless optimization, their enterprise pricing tiers can quickly become prohibitive for growing startups and independent developers. Fortunately, you can build your own high-performance, real-time (on-the-fly) image optimization system. By pairing Imgproxy (a lightning-fast, secure image processing engine written in Go) with Caddy Server (a modern, production-ready web server with automatic SSL), you can deploy a self-hosted CDN on a budget-friendly Virtual Private Server (VPS) that rivals commercial solutions.
Why Imgproxy and Caddy Server?
Before diving into the technical implementation, it is crucial to understand why this specific technology stack is highly effective for image processing:
- Imgproxy Efficiency: Unlike traditional image processing libraries that consume massive amounts of memory, Imgproxy is designed to be stateless and incredibly memory-efficient. It processes images on-the-fly without saving them to disk, utilizing Libvips under the hood for unmatched speed.
- Security First: Imgproxy mitigates remote attack vectors by verifying image URLs using cryptographic signatures (HMAC), preventing attackers from exploiting your server via URL manipulation.
- Caddy Server Simplicity: Caddy acts as a reverse proxy and caching layer. It handles automatic Let's Encrypt SSL certificates out of the box, boasts a clean configuration syntax, and supports HTTP/3 by default for rapid asset delivery.
System Architecture Overview
The workflow of our self-hosted real-time image CDN operates through a structured pipeline:
- The user's browser requests an image via a structured URL specifying desired dimensions and format (e.g.,
/resize/w:800/format:webp/plain/[https://example.com/origin.jpg](https://example.com/origin.jpg)). - Caddy Server intercepts the request. If the optimized image exists in Caddy's cache, it is served instantly.
- If it is a cache miss, Caddy forwards the request to Imgproxy.
- Imgproxy fetches the original asset from your remote storage (such as AWS S3, DigitalOcean Spaces, or a standard web server), processes it in memory (resizing, compressing, converting to WebP/AVIF), and returns it to Caddy.
- Caddy caches the response and delivers the optimized image to the end user.
Step 1: Setting Up Imgproxy with Docker
Deploying Imgproxy via Docker is the most reliable method, ensuring all native graphics dependencies like Libvips are properly configured. Create a dedicated directory on your VPS and define a docker-compose.yml file:
version: '3.8'
services:
imgproxy:
image: darthsim/imgproxy:latest
container_name: imgproxy
restart: always
ports:
- "127.0.0.1:8080:8080"
environment:
- IMGPROXY_BIND=0.0.0.0:8080
- IMGPROXY_LOCAL_FILESYSTEM_ROOT=/mnt/images
- IMGPROXY_KEY=your_hex_encoded_key_here
- IMGPROXY_SALT=your_hex_encoded_salt_here
- IMGPROXY_ENABLE_WEBP_DETECTION=true
- IMGPROXY_ENABLE_AVIF_DETECTION=true
- IMGPROXY_MAX_SRC_RESOLUTION=50
Note: For production environments, always generate strong cryptographic keys usingecho -n "secret" | xxd -ps -c 200to populate yourIMGPROXY_KEYandIMGPROXY_SALT. This ensures malicious actors cannot abuse your endpoint to resize arbitrary images.
Step 2: Configuring Caddy Server as a Reverse Proxy and Cache
With Imgproxy listening locally on port 8080, we configure Caddy Server to handle external HTTPS traffic, manage SSL certificates, and cache the processed assets. Modern installations of Caddy can utilize the http.cache plugin to handle caching directly in memory or on disk.
Edit your Caddyfile with the following configuration architecture:
cdn.yourdomain.com {
encode gzip zstd
# Enable Caching layer
cache {
match_path /cdn/
ttl 7d
}
# Reverse proxy to the internal Imgproxy service
handle /cdn/* {
uri strip_prefix /cdn
reverse_proxy 127.0.0.1:8080 {
header_up Host {http.reverse_proxy.upstream.hostport}
header_down Cache-Control "public, max-age=31536000, immutable"
}
}
# Fallback error handling
handle {
respond "Access Denied" 403
}
}
This configuration ensures that all successfully processed images receive a long-lived Cache-Control header, instructing both downstream browsers and edge networks to cache the asset for up to one year, drastically reducing repetitive CPU load on your VPS.
Step 3: Generating and Verifying Signed URLs
When URL signature protection is enabled, Imgproxy expects a specific URL format where a cryptographic signature is prepended to the processing path. The production URL structure follows this pattern:
[https://cdn.yourdomain.com/cdn/](https://cdn.yourdomain.com/cdn/){signature}/{processing_options}/{encoded_source_url}
You can generate these signatures within your application backend (Node.js, Python, PHP, or Go). Below is an architectural overview of how a signature is securely generated using HMAC-SHA256:
// Pseudocode for URL Signing
path = "/resize:fill:800:600/plain/[https://example.com/image.jpg](https://example.com/image.jpg)"
message = salt + path
signature = hmac_sha256(key, message).base64_url_encode()
final_url = "[https://cdn.yourdomain.com/cdn/](https://cdn.yourdomain.com/cdn/)" + signature + path
By enforcing this workflow, your architecture remains highly secure against distributed denial-of-service (DDoS) vectors targeting image manipulation parameters.
Performance Optimization and Tuning
To extract maximum efficiency from your self-hosted setup, consider applying the following production tunings to your VPS:
- Leverage AVIF and WebP: Ensure your application requests images with
format:aviforformat:webpfallback logic. AVIF images generally achieve a 30-50% smaller file size footprint compared to standard JPEGs without sacrificing visual quality. - Concurrency Controls: Adjust the
IMGPROXY_CONCURRENCYenvironment variable based on your CPU core availability. A standard thumb rule is setting this value equal to your available CPU cores to prevent thread starvation under heavy loads. - Origin Shielding: Place your source images behind an object storage system with private access permissions, configuring Imgproxy with necessary IAM credentials to pull source files securely rather than exposing public URLs.
Conclusion
Building a real-time image optimization CDN with Imgproxy and Caddy Server provides a robust, enterprise-grade infrastructure on your own terms. It gives you absolute control over your data lifecycle, slashes external SaaS costs, and substantially improves page load times for end users. By offloading resource-heavy computations to a highly efficient Go-based architecture and caching outputs aggressively at the web server layer, your VPS can easily scale to serve millions of optimized images daily.
