Push / static
Files live in /data/static/<zone>/. Served with sendfile. Do not proxy_cache a local file. ETag from size + mtime. Directory listing is off by default. Upload from the dashboard or rsync over SSH.
cdn-server is a KeyCDN-compatible edge: pull from an origin, or serve a project’s files from disk. Cache lives on each node. Add another server from the operator dashboard. Ansible installs Docker and copies the data that must be shared. The cache is not copied — each edge fills itself on demand.
First launch covers CSS, JS, fonts, images, and other static files on a fast URL. Streaming and HLS are out of scope.
Two roles. One writer. No Redis, Postgres, Varnish, or extra app servers on the hot path.
| Control | One machine. Go binary owns the dashboard, management API, SQLite, config snapshots, certificates, and Ansible job files. Canonical copy of push/static files lives here. |
|---|---|
| Edge | OpenResty (nginx + LuaJIT). sendfile, open_file_cache, proxy_cache, gzip, HTTP/2, range requests. Lua selects the zone from the Host header and applies a JSON snapshot. No second proxy in front. |
| Runner | ansible-core in a container. The public control process does not mount the Docker socket. It writes a job file; the runner has the socket, SSH key, and inventory. |
| Data | SQLite in WAL mode for zones, aliases, referrers, nodes, API keys, and jobs. One writer. Edges never write this database. Files live under /data. |
Zone names are lowercase alphanumeric, 1–20 characters, unique — the same constraint as KeyCDN so the API field matches.
Files live in /data/static/<zone>/. Served with sendfile. Do not proxy_cache a local file. ETag from size + mtime. Directory listing is off by default. Upload from the dashboard or rsync over SSH.
Origin URL is scheme + host only. The request path is appended. Query string is forwarded to origin even when ignored for the cache key. Connect timeout 2s, read timeout 10s. Responses above 100 MB are streamed, not stored (X-Cache: BYPASS).
This is the core of the product. Two cache roots so a wave of images cannot evict CSS and fonts.
| Static path | open_file_cache max=100000. sendfile, tcp_nopush, gzip_static. A request is a disk read plus headers. |
|---|---|
| Pull caches | /data/cache/image 50g and /data/cache/other 20g. Image cache when Content-Type starts with image/ or the extension is jpg, png, gif, webp, avif, ico, svg. |
| Default key | $zone_id|$uri. Query string ignored. Scheme, host, cookie, device, country, and webp are not in the key unless you enable them on the zone. |
| Stale / lock | proxy_cache_lock, revalidate, background update, and use_stale on error/timeout/5xx. Valid 200/301/302 for 30d when ignore-cache-control is on. 404 for 1 minute. |
| Browser expire | Same meaning as KeyCDN. -1 no-cache; 0 honor origin (pull) or skip rewrite (push); >0 Cache-Control max-age in minutes. Hash-named assets get immutable, 1 year. |
| Purge | Zone or URL. Control fans out POST /internal/purge to every active edge. Snapshot carries a monotonic purge_generation as the correctness backstop. Browser caches are not purged. |
Shared wildcard on the zone domain, Let’s Encrypt for one alias per zone (issued by the control plane with lego), or a custom PEM on an alias. Optional HTTP→HTTPS 301 per zone. HTTP/2 on. HTTP/3 is later.
Referrer allow/deny, empty referrer toggle, secure token (optional bind-to-IP), CORS *, force download, block bad bots (451). Applied in Lua from the snapshot — not in your origin app.
Server: cdn-server. X-Cache HIT|MISS|EXPIRED|STALE|BYPASS|UPDATING. X-Edge-Location. Accept-Ranges: bytes. Vary: Accept-Encoding only on compressed responses.
There is no anycast and no BGP. Each edge has a public IP. DNS round-robin is the scale-out method.
HTTP Basic. Username is the API key, password empty. Zones, aliases, referrers, purge, reports, and credits follow the KeyCDN envelope, parameters, and error codes.
Traffic, cache status, countries, and top paths from edge access logs ingested by control. Usage logs in the panel. Credits settle against the assigned package.
Zones, traffic, storage, and upload size are enforced by the control plane. Credits top up bandwidth. Operator accounts manage users, packages, redeem codes, and nodes.
Optional. imgproxy (Go + libvips) runs only when a zone has image processing enabled and the request carries transform query parameters. nginx caches the transformed bytes so libvips runs once per variant.
Streaming, HLS, CMS plugins, anycast, and HTTP/3 are catalogued for later and are not required to launch. The stack does not include Redis, Elasticsearch, Kafka, MinIO, Varnish, Node, PHP, or Python app servers. Postgres is a possible later swap only if two control-plane writers are required.
Create a customer account, add a zone, point DNS, purge when content changes. Operators sign in separately to manage nodes and packages.