Sizing & Infrastructure Requirements
Cyferio is a single-host architecture: the OpenVPN daemon, its PKI
(easy-rsa), the per-client policy/usage files, and the web admin all run
co-located on one machine (see Architecture).
There’s no built-in clustering or horizontal scaling – sizing guidance
below is about choosing the right size for that one box, not about
provisioning a fleet. If you need multiple independent VPN gateways, run
multiple independent Cyferio deployments, each with its own PKI and policy
state.
The numbers on this page aren’t estimates – they’re measured from two real deployments: a lean 1-vCPU/1GB production box (19 active clients, 143 days of continuous uptime) and a 2-vCPU/8GB box with comfortable headroom.
Component footprint (measured)
Section titled “Component footprint (measured)”| Component | RAM (steady state) | CPU (steady state) | Disk |
|---|---|---|---|
Web admin (app, FastAPI/uvicorn) |
120-150 MB | Near 0%, single-core spikes – see below | 227 MB image (55 MB compressed pull) |
| Traefik (TLS/reverse proxy) | 15-20 MB | Negligible | ACME cert storage: a few KB |
| PostgreSQL (if used) | 24-40 MB | Negligible | 8.5 MB after 143 days / 19 clients / 6 users – see Database |
| OpenVPN daemon (host, not containerized) | 7-12 MB RSS, flat across 9-19 clients tested | Scales with encrypted throughput, not client count | ~100 MB total under /etc/openvpn (PKI + logs + optional GeoIP DBs) |
OpenVPN does not fork a process per client – it’s a single event-driven daemon, so its own RAM/CPU footprint barely moves between a handful of clients and a few dozen. What actually scales with client count and usage is network egress and, under sustained heavy simultaneous throughput, encryption CPU – not memory.
The one real CPU spike
Section titled “The one real CPU spike”Building the city/ASN login-restriction pick-lists (walking the full
GeoLite2-City/ASN databases) takes roughly 90-100s + 15s, single-core-bound.
This happens once at app startup and again automatically whenever the
underlying .mmdb file’s modified time changes (i.e., after a GeoIP
database refresh). It only matters if you use city/ASN restrictions
(country and IP restrictions don’t trigger it). Outside of this, combined
CPU across every container plus the OpenVPN daemon sits under 5-10% of a
single core at light-to-moderate usage.
Minimum
Section titled “Minimum”Validated by a real production deployment: 19 active clients, 143 days of continuous uptime, no crashes.
- 1 vCPU
- 1 GB RAM + 2 GB swap – swap is not optional at this tier. The reference box relies on it; the app container alone uses 100-150 MB, which is a meaningful fraction of 1 GB once the OS, Docker, and Traefik/Postgres are accounted for.
- 20-25 GB SSD – covers OS, Docker images, and years of log/PKI growth at light usage (see Storage growth)
- SQLite (default, zero extra setup)
- 1 public IPv4; inbound reachability on the OpenVPN port (default UDP 1194) and TCP 80/443
Good for a small team – roughly up to 20-25 VPN clients, a handful of admin/portal users, light-to-moderate concurrent throughput (the reference box averaged ~120 MB/day of actual VPN traffic).
Recommended
Section titled “Recommended”For a growing deployment where you’d rather not watch memory headroom.
- 2 vCPU
- 4 GB RAM – comfortable headroom for the CPU spike above without it contending with normal request traffic, plus room for Postgres
- 40 GB SSD
- PostgreSQL (bundled
postgresCompose service, switch viaDATABASE_URL) once more than one admin is writing concurrently – see Database - Same networking as Minimum
Good for dozens to roughly 100 VPN clients, several concurrent admin/portal sessions, and comfortable headroom for GeoIP city/ASN restrictions.
Production-scale
Section titled “Production-scale”Beyond the Recommended tier, sizing becomes less about hitting a bigger number and more about durability and visibility – this app’s own footprint barely grows with scale (see the measurements above), but the operational surface around it does.
- 2-4 vCPU – driven by peak aggregate concurrent encrypted throughput, not client count. A modern core comfortably handles well over 100 Mbps of AES-256-GCM OpenVPN traffic; budget cores against expected simultaneous heavy usage (video, large transfers), not connection count.
- 4-8 GB RAM
- 80-100+ GB SSD – headroom for the OS’s own systemd journal (which can independently reach several hundred MB-1 GB over months, unrelated to Cyferio itself) plus years of audit-log and connection-log growth
- PostgreSQL required, not SQLite – SQLite’s file-level write lock becomes a real constraint once multiple admins/portal users write concurrently (audit-log entries, profile updates, restriction changes). This is a concurrency decision, not a disk-size one: even at this scale, expect the database itself to stay in the tens-to-low-hundreds of MB for years (see below).
- Automated Postgres backups – not bundled by the app. A simple
pg_dumpon a cron/systemd timer to off-host storage is the pragmatic baseline. - External uptime/alerting – the built-in Health/Diagnostics pages are live snapshots, not historical or alerting (see Monitoring). Point an external checker at the HTTPS admin URL.
- Consider
uvicorn --workers Nif concurrent portal-login load grows significantly – the app runs a single worker process by default (seeapp/Dockerfile’sCMD), which is fine for the admin-tool-sized concurrency this app usually sees, but is a real scaling knob if a large self-service portal user base starts logging in simultaneously. - Log rotation for
/etc/openvpn/server/openvpn.log– at the reference box’s actual usage (verb 3, 19 clients) this grew to only 823 KB over 143 days (roughly 2 MB/year). No rotation is configured out of the box today. Fine to leave alone at similar usage levels; worth adding alogrotateentry if verbosity or client count grows substantially past what’s been measured here.
Networking
Section titled “Networking”- Inbound, required: the OpenVPN port (default UDP 1194, configurable to TCP/a different port during install) and TCP 443 for the web admin/portal. TCP 80 is used for the initial Let’s Encrypt HTTP-01 challenge and Traefik’s HTTP→HTTPS redirect – leave it open rather than closing it after first issuance.
- Egress bandwidth is the real scaling cost, not connection count. Budget per concurrent active client by expected usage pattern (light browsing/email vs. video/large transfers), not a fixed per-connection number – OpenVPN’s per-client memory/CPU overhead is negligible at any realistic client count (see the component table above).
- Cloudflare-fronting is supported for the web admin/portal
(
CLIENT_IP_TRUST_MIDDLEWARE=cloudflare-onlyin.env, enforced at the Traefik layer) but doesn’t apply to the OpenVPN port itself – Cloudflare’s proxy doesn’t front arbitrary UDP, so the VPN port is always directly internet-facing.
Database
Section titled “Database”- SQLite (default): zero setup, a single file under
app/data/. Fine through Minimum and Recommended. Only stores this app’s own accounts and audit log – VPN client data always comes live from the CLI scripts, so database size is never driven by client count. - PostgreSQL (bundled Compose service,
DATABASE_URLin.env): required once concurrent writes matter (multiple admins, an active self-service portal). Real measured size: 8.5 MB after 143 days in production with 19 VPN clients, 6 admin/portal users, 150 audit-log rows, and 286 dashboard stat snapshots. Expect low-hundreds-of-MB even at multi-year, heavier-usage production scale – this is a concurrency and durability decision, not a storage-sizing one. - No managed/external database service is required at any tier covered here – the bundled container is sufficient for any realistically-sized single-host deployment. Only reach for a managed DB if you specifically want backups/failover decoupled from the app host’s own disk.
- Audit-log retention is configurable at runtime from the Settings page – the one piece of log-growth management that’s actually built in.
Monitoring
Section titled “Monitoring”What’s built in today, and what isn’t:
- Health page reads the host’s live CPU/RAM/disk (via read-only
bind-mounts of
/proc,/sys, and/) plus Traefik’s internal health API – a real-time snapshot, refreshed on page load. - Diagnostics page surfaces live VPN status (
vpn-status.py --json) and rejected-connection history. - Neither of these retains history or alerts you – there’s no bundled metrics store, and nothing pages anyone.
For production-scale deployments, layer on:
- Uptime/alerting: an external checker (Cloudflare Health Checks, UptimeRobot, or similar) against the HTTPS admin URL is the pragmatic minimum – catches the host being down or Traefik/the app crashing.
- Historical metrics (optional): a standard
node_exporter+ Prometheus + Grafana stack on the host, if trending CPU/RAM/disk over time matters to you. This is not integrated by Cyferio – it’s a general-purpose Linux-box addition, same as you’d add to any VM. - Database backups: see Database above – not automated by the app itself.
Storage growth over time
Section titled “Storage growth over time”Concrete numbers, not projections, from the 143-day reference deployment:
| Item | Measured | Notes |
|---|---|---|
PKI (easy-rsa/pki) |
388 KB for 9 clients | Scales linearly and trivially – roughly 40 KB/client |
| GeoIP databases (optional) | 8.3 MB (Country) + 12 MB (ASN) + 63 MB (City) = ~83 MB | Fixed size, replaced in place on weekly refresh – doesn’t accumulate |
| OpenVPN connect/reject log | 823 KB over 143 days, verb 3, 19 clients | ~2 MB/year at this activity level; truncated on daemon restart, not unbounded within a boot |
| PostgreSQL database | 8.5 MB over 143 days | See Database |
| Actual VPN traffic (this deployment) | ~17 GB total (2 GB in / 15 GB out) over 143 days | ~120 MB/day average – a light-usage reference point, not a stress test upper bound |
None of these are the reason to size disk generously – the OS, Docker images/layers, and (at production scale) the systemd journal are the actual drivers of disk usage over time, not anything Cyferio itself writes.