ServeProxy CDN documentation
Point your domain at the CDN, give it your origin, and static files are cached at the edge. Purge them with one API call. Bandwidth is metered per location.
Overview
ServeProxy CDN is a multi-node caching proxy. Static assets are cached at the edge and served from the closest available node.
A cache miss fetches the file from your origin, stores the response and serves it. Later requests are served from cache without touching your origin.
Each CDN location maps one or more hostnames to a single origin. You manage locations from the dashboard.
Getting started
- Create a location. Sign in to the dashboard and add a location with your origin URL, for example
https://origin.example.com. - Note your CDN hostname. The dashboard assigns a hostname under
.cdn.serveproxy.com. You can also attach a custom domain. - Save your API key. Each location has its own key, used for purges and stats.
- Point your DNS at the CDN. See DNS setup.
- Test it. Request a static file through the CDN hostname and check the response headers.
DNS setup
A custom domain must resolve by CNAME to a hostname under .cdn.serveproxy.com. The CDN checks this on its next config sync. Until the check passes, the domain is skipped and won't serve traffic.
assets.example.com. CNAME your-location.cdn.serveproxy.com.
Once the CNAME resolves correctly, a TLS certificate is issued and the domain goes live within a minute or two.
If you use Cloudflare for DNS, set this record to DNS only (grey cloud). Proxying through Cloudflare on top of the CDN breaks verification.
Cache behaviour
What gets cached
Only GET requests for static file extensions are cached. Everything else is passed straight through to your origin.
Cached extensions include images (jpg png webp avif svg gif ico), fonts (woff woff2 ttf eot otf), scripts and styles (css js mjs map wasm), media (mp4 webm mp3 ogg wav flac) and documents (pdf json xml txt csv zip).
How long
If your origin sends Cache-Control: max-age=N, the CDN uses it. Responses marked no-store or private are not cached. Without a header, these defaults apply:
| Content type | Default TTL |
|---|---|
| Images (jpg, png, webp, avif, svg, gif, ico) | 7 days |
| CSS, JS, fonts | 30 days |
| JSON, XML | 1 hour |
| Everything else cacheable | 1 hour |
Stale-while-revalidate
When an entry has expired but is still inside its stale window, the CDN serves the cached copy immediately and refreshes it from your origin in the background. If your origin sends stale-while-revalidate=N, that value is used; otherwise the window equals the TTL.
Conditional requests
Background refreshes send If-None-Match and If-Modified-Since when the cached entry has an ETag or Last-Modified. A 304 Not Modified reply saves bandwidth and simply extends the TTL.
Response headers
| Header | Meaning |
|---|---|
X-Cache | HIT if served from cache, MISS if fetched from your origin |
X-Cache-Age | How long the cached entry has been stored, in seconds |
X-Served-By | The CDN node that served the request |
curl -I https://your-location.cdn.serveproxy.com/logo.png
Purge API
Clears cached entries on every node.
A request to one node is passed on to all the others, so you only need to call it once.
GET https://your-location.cdn.serveproxy.com/_cdn/purge
| Parameter | Description |
|---|---|
key | Required. Your location's API key. |
url | Purge a single URL. Pass the path including any query string. |
all | Set to true to clear the whole cache for this location. |
Either url or all=true must be set.
Purge one file
curl "https://your-location.cdn.serveproxy.com/_cdn/purge?key=YOUR_KEY&url=/images/logo.png"
Purge everything
curl "https://your-location.cdn.serveproxy.com/_cdn/purge?key=YOUR_KEY&all=true"
Response
JSON with the totals from all nodes combined:
{
"ok": true,
"hostname": "edge-01",
"host": "your-location.cdn.serveproxy.com",
"backend": "https://origin.example.com",
"action": "url",
"url": "/images/logo.png",
"total_entries": 4,
"total_freed": "1.2 MB",
"duration_ms": 87,
"timestamp": "2026-05-27T10:15:00Z"
}
The API key travels in the query string. Don't paste purge URLs into chat logs, issue trackers or anywhere that gets archived. If a key leaks, regenerate it from the dashboard.
Stats API
Returns cache statistics for your location, summed across all nodes. Useful for monitoring.
curl "https://your-location.cdn.serveproxy.com/_cdn/stats?key=YOUR_KEY"
{
"ok": true,
"hostname": "edge-01",
"host": "your-location.cdn.serveproxy.com",
"backend": "https://origin.example.com",
"cached_urls": 6240,
"total_size": "2.4 GB",
"timestamp": "2026-05-27T10:15:00Z"
}
Each node caches these numbers for 30 seconds, so values may lag briefly after a purge.
Rate limits
Each client IP may make 500 requests per second, with bursts up to 1,000. Clients over the limit receive 429 Too Many Requests with a Retry-After header.
The limit applies to every endpoint, including /_cdn/purge and /_cdn/stats. It is counted per node, so the effective ceiling across the network is higher.
Troubleshooting
A custom domain returns 502
The CDN only serves domains that resolve by CNAME to .cdn.serveproxy.com. Check the record with dig CNAME yourdomain.com. Verification retries on every config sync, every 30 seconds.
X-Cache always shows MISS
The file may not have a cached extension, your origin may send Cache-Control: no-store, or each request may land on a different node. Caches fill per node as traffic arrives.
Old content is still served after a purge
Check total_entries in the purge response. If it's 0, the entry was already gone or the URL didn't match. The url parameter must match exactly, including the query string.
Origin changes don't show up
Static assets are cached for up to 30 days by default. Send a shorter Cache-Control: max-age from your origin, version your asset URLs, or call the purge API after each deploy.
The first request on a new node is slow
The first request for a URL on each node is always a miss and goes to your origin. After that the node serves it from cache, and stale-while-revalidate keeps responses fast when entries expire.
Support
Questions, bug reports and feature requests: [email protected]