Skip to content

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 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.

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.

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).

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 postgres Compose service, switch via DATABASE_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.

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_dump on 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 N if concurrent portal-login load grows significantly – the app runs a single worker process by default (see app/Dockerfile’s CMD), 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 a logrotate entry if verbosity or client count grows substantially past what’s been measured here.
  • 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-only in .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.
  • 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_URL in .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.

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.

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.