DEV Community

Cover image for A year solo-building a real VPN: stack, abuse controls, and what broke in production.
Artem Mishurovskyi
Artem Mishurovskyi

Posted on

A year solo-building a real VPN: stack, abuse controls, and what broke in production.

Hi — I'm a full-stack engineer. For almost a year I've been solo-building FollowNet: iOS VPN client, Chrome extension, NestJS backend, VPN nodes, billing, monitoring.

Not a landing-page MVP post — this is what the stack looks like and what broke after shipping.

What it is

  • iOS: IKEv2, WireGuard, AmneziaWG, Hysteria2, DNS profiles, auto-connect, Shortcuts, Smart Connect (pick protocol/server from network context)
  • Chrome: browser proxy via SOCKS/gateway (not a system-wide VPN)
  • Live in the App Store

Architecture (why modular monolith)
I'm one person. One NestJS API with domain modules beats microservices for me — fewer moving parts at 2am.

  • Postgres (Prisma) = source of truth
  • Redis = rate limits, bans, short-lived state, pub/sub
  • FreeRADIUS = who gets into VPN and with which group (anon / free / premium)
  • Nodes provisioned with Ansible (StrongSwan / WG / Amnezia / Hy2)
  • Billing: StoreKit 2 + Apple Server Notifications on iOS; WayForPay on web

Protocol evolution
IKEv2 first (fastest path on iOS without a custom tunnel). Then WireGuard as the default dataplane. Then AmneziaWG + Hysteria2 for networks where plain UDP dies.

On iOS each protocol is a separate Network Extension target. Easier to debug early; cost is duplicated health-check / quota / reconnect logic. If I started today I'd put one Libbox-based core under them.

Smart Connect isn't "nearest ping wins" — geo/ASN rules + client success/fail telemetry so we don't start with a protocol that usually fails on that network.

Auth / access is a chain, not a flag
Three different credentials:

  1. App JWT → API
  2. RADIUS user/pass → VPN server (esp. IKEv2)
  3. Short-lived proxy JWT (~4h) → Chrome SOCKS gateway

Weekly free traffic resets Monday 00:00 UTC. Device slots: 2 free / 5 premium. Oldest lastSeenAt gets kicked when full.

Biggest pain: state drift. API says premium, RADIUS still free (or the reverse). Cron sync + Socket.IO pushes + sessionId so an old disconnect doesn't kill a fresh reconnect.

Backend abuse controls (honest scope)
Not "anti-DDoS for everything". Mostly API abuse:

  • Helmet, CORS allowlist, DTO validation
  • Redis sliding-window rate limits with escalating bans
  • Disposable email blocklist
  • Traffic exhausted → notify client → RADIUS CoA → (WG/AWG only) SSH peer remove as narrow fallback if CoA fails
  • Apple JWS + WayForPay HMAC webhooks

Certificate pinning + dual API mirrors (.com / .net) on the client — some networks blackhole one host and the app looks "dead" even when VPN nodes are fine.

Grafana: VPN connects and CoA metrics

Admin Panel

What actually broke in prod

  1. Connected but no internet — green tunnel, dead traffic. Fix: health monitors in extensions + quota gate at tunnel start.
  2. API config ≠ VPN access — RADIUS out of sync. Fix: CoA retries, CoA agent on node, sessionId race guards.
  3. LTE↔Wi-Fi handover — too-aggressive health checks flap; too soft = sit on a dead tunnel.
  4. Node deploy — Ansible helps; Amnezia installs can reboot forever; every new NAS must be in RADIUS clients or IKEv2 EAP times out.
  5. Chrome is a different product — no system VPN API; SOCKS + TLS gateway + Apple Sign-In relay via backend.
  6. Apple CONSUMPTION_REQUEST on refunds — answer with session usage, then on REFUND strip premium + update RADIUS.

What I'd do differently

  • One shared session model across client / API / VPN from month one
  • One Libbox core instead of multiple NE targets early
  • Stabilize WG + accounting before Amnezia/Hy2
  • More synthetic probes, fewer "wait for tickets"
  • Write down "why this way" from the start

Three notes to past me

  1. API OK ≠ VPN works — design the chain to RADIUS/node, not just config download
  2. Connected ≠ internet — health + quota belong in the tunnel
  3. Don't multiply protocols/targets early — one stable path + accounting first

Happy to answer engineering questions. What would you redesign first on a solo VPN?

If you want to see the product: follow-net.com

Top comments (0)