Pritunl high availability cluster
Why this product
A VPN endpoint is infrastructure other teams depend on; a single VM is a single point of failure. This product turns Pritunl OSS into a true highly available service on Yandex Cloud:
- Zero-touch setup. On first boot the deployment configures itself: it creates the VPN server, organization, and first user, opens routes to your VPC subnets, points clients at the VIP, and stores the admin and API credentials in Lockbox. No web-UI wizard, no SSH, no
default-password— read the credentials, download the first profile, connect. (Most HA Pritunl setups are configured by hand in the web UI on every deploy.) - REST API enabled out of the box. Pritunl’s HMAC REST API is turned on automatically with the token and secret in Lockbox, so you can script users, profiles, and server changes from day one — no manual enablement.
- Real shared state, not config sync. All Pritunl data (users, servers, organizations) lives in a 3-host Yandex Managed MongoDB cluster. Both nodes see the same configuration immediately; there is no replication lag and no sync mechanism to break.
- Split-brain-safe failover. Leader election uses an atomic MongoDB lease with monotonic fencing tokens. Two nodes can never be active at once, and a stale ex-leader cannot rewrite routes after losing the lease.
- Both traffic paths flip together. Inbound traffic follows the network load balancer health check; return traffic follows a VPC route table next-hop. The failover daemon switches both atomically with the leadership, so traffic never converges on a half-failed node.
- Clients keep their identity. Routed mode without source NAT: VPN clients appear inside the VPC under their real VPN addresses, so security groups, firewall rules, and audit logs work per client.
- Cloud-native and keyless. No service-account key files: the daemon uses short-lived IAM tokens from the instance metadata service, and all secrets live in Yandex Lockbox.
- Built for Yandex Cloud specifically. Pritunl’s own multi-cloud tooling does not integrate with Yandex Cloud; this product fills that gap with native YC route-table failover, Managed MongoDB, NLB health-gating, and Lockbox — HA VPN that works the Yandex way out of the box.
Full description
Pritunl HA deploys two Pritunl OSS VPN servers (Ubuntu 24.04) as an active-passive pair in different availability zones, fronted by one static public VIP on a network load balancer. A 3-host Yandex Managed MongoDB cluster (TLS, one host per availability zone) stores all Pritunl state and the leadership lease, so both nodes always serve an identical configuration.
A failover daemon runs on both nodes. Every ~5 seconds each node attempts to acquire or renew a MongoDB lease (TTL 15 seconds) through an atomic conditional update with a monotonic fencing token. The lease holder, when its local health probe passes, ensures Pritunl is running, rewrites the VPC route table next-hop for the VPN client pool to itself, and reports healthy (/healthz = 200) to the load balancer. Every other node reports 503 and stays passive.
Failover covers both traffic directions at once. Inbound (client to VPN): the NLB forwards client traffic only to the node passing the health check. Return (VPC resource to VPN client): the route table next-hop for the client pool CIDR points at the active node’s internal IP and is rewritten through the Yandex Cloud API on takeover. Every route mutation carries the fencing token, so a stale ex-leader is rejected.
The daemon fails closed: if any post-acquire step fails (Pritunl not running, IAM token unavailable, route flip rejected), the node reports 503 and actively releases the lease so a healthy peer takes over immediately instead of waiting out the TTL. On any doubt a node goes passive; serving inbound traffic without the return path is never allowed. There is no automatic failback — the recovered node stays passive until the active one fails, which prevents flapping.
End-to-end convergence is about 20-40 seconds. The OpenVPN server holds per-host state, so a failover is a client rehandshake to the same VIP and reconnects within the client’s keepalive window. OpenVPN runs over TCP, which rides the load balancer’s reliable TCP path.
Key features
- Active-passive Pritunl OSS pair across two availability zones, one static public VIP.
- Zero-touch setup: the VPN server, organization, and first user are created automatically on first boot; admin and API credentials land in Lockbox; the Pritunl REST API is enabled. No web-UI wizard, no SSH.
- OpenVPN over TCP (port 1194), which rides the NLB’s TCP path that Yandex distributes reliably. WireGuard and the OpenVPN-over-UDP option are deferred until Yandex NLB UDP load-balancing is generally available (see Limitations).
- All state in a 3-host Yandex Managed MongoDB 8.0 cluster (TLS, PRODUCTION) — no config sync, no shared filesystem.
- Atomic MongoDB lease with fencing tokens for leader election: no split-brain, a stale ex-leader cannot mutate routes.
- Dual-path failover: NLB health gate for inbound, VPC route table next-hop for return traffic, switched together in ~20-40 seconds.
- Fail-closed daemon: on any failure the node goes passive and yields the lease; no automatic failback, no flapping.
- Routed mode without source NAT: VPN clients are visible in the VPC under their real VPN addresses for firewall rules and audit.
- Zero-touch HA tuning: the server is automatically replicated to both nodes (hot standby), the request thread pool is sized to the node’s CPU count, and the OpenVPN ping timeouts are lowered for fast client reconnect after failover — no manual server configuration for any of it.
- Two-factor authentication built in: per-user PIN plus OTP (Google Authenticator). With a standard OpenVPN client you enter PIN+OTP as the connection password (no proprietary client needed).
- Separate web-UI load balancer: the admin console has its own VIP that reaches whichever node is up (independent of which node is the active VPN data plane), with optional Let’s Encrypt for a trusted certificate across both nodes.
- OpenVPN over TCP rides the load balancer’s reliable TCP path and works end-to-end through the VIP. WireGuard and OpenVPN-over-UDP are planned for when Yandex NLB UDP load-balancing is generally available (see Limitations).
- Keyless: short-lived IAM tokens from instance metadata; secrets (MongoDB URI, CA) in Yandex Lockbox; least-privilege service account (
lockbox.payloadViewer,vpc.privateAdmin). - SSH key-only access; password authentication disabled.
Before you deploy
The product creates the VPN pair and its HA infrastructure (two VMs, service account, security group, NLB with VIP, Managed MongoDB cluster, route table, Lockbox secret with the connection URI). The resources below are deliberately not created — they are long-lived or security-sensitive and you control them. Prepare them first:
- Three subnets in one VPC network, one in each availability zone: zones a and b host the VPN nodes, zone d hosts the third MongoDB replica. All three must belong to the same network — the security group, the route table, and the load balancer operate within a single network.
- Two free internal IP addresses, one inside the zone-a subnet and one inside the zone-b subnet. They become the node addresses, the NLB targets, and the route next-hops, so they are fixed at deployment and must not be in use.
- Lockbox secret with the MongoDB password — the password for the dedicated
pritunldatabase user. The deployment reads the first text entry of the secret’s current version. See “Creating the MongoDB password secret” below. - SSH public key for the
ubuntuuser — break-glass access for debugging and for reading the initial Pritunl credentials. - A client pool CIDR outside all VPC subnet ranges (for example
172.18.0.0/22if your VPC uses10.0.0.0/8). VPN clients receive addresses from this pool; it is routed through the VPC route table, so it must not overlap any subnet.
Creating the MongoDB password secret
The deployment uses the first text entry of the secret’s current version as the password of the pritunl MongoDB user. Create the secret with the Yandex Cloud CLI:
yc lockbox secret create \
--name pritunl-mongo-password \
--payload '[{"key":"password","text_value":"<your-mongodb-password>"}]'
In the console: Lockbox -> Create secret -> add one key/value entry with the password as the value. Select this secret in the mongo_password_secret field of the deployment form.
Deployment parameters
These are the fields shown in the deployment form:
- Deployment name — prefix for all created resources (pattern
^[a-z][a-z0-9-]{2,30}$). - Subnet (zone a) — subnet for the first VPN node and the first MongoDB host.
- Subnet (zone b) — subnet for the second VPN node and the second MongoDB host (different availability zone).
- Subnet (zone d) — subnet for the third MongoDB host (third availability zone).
- Node A internal IP — free internal IPv4 address inside the zone-a subnet.
- Node B internal IP — free internal IPv4 address inside the zone-b subnet.
- MongoDB password secret — Lockbox secret holding the MongoDB user password (first text entry of the current version).
- SSH public key — public key injected into both VMs for the
ubuntuuser. - Administrator email — email for the first VPN user created automatically (its local part becomes the user name).
- VPN encryption (optional) —
standard(AES-128/SHA1, default) orstrong(AES-256/SHA256). - Two-factor auth (PIN+OTP) (optional, default off) — requires OTP on the server and gives the first user a managed PIN; the PIN and the user’s OTP secret are written to the api-credentials Lockbox secret (
USER_PIN,USER_OTP_SECRET). Works with standard clients: the connection password is PIN + the current OTP code. - DNS servers (optional, default
8.8.8.8) — comma-separated DNS servers pushed to clients (e.g. an internal resolver). - DNS search domain (optional) — pushed to clients.
- Client-to-client traffic (optional, default on) — off isolates clients from each other.
- Web console SSL certificate (optional) — a Lockbox secret holding
WEB_SSL_CERT(full PEM chain) andWEB_SSL_KEY; the web console serves it on both nodes instead of the self-signed certificate. Blank keeps the self-signed certificate (or use the Let’s Encrypt option in Settings). The private key stays in Lockbox and never enters VM metadata. - VPN client pool CIDR — address pool for VPN clients; must be outside all VPC subnet ranges.
- Environment —
prodordev; selects VM, disk, and MongoDB cluster sizing (see Architecture).
Ingress is open to 0.0.0.0/0 on TCP 1194 (OpenVPN), TCP 443 (Pritunl web UI HTTPS), TCP 22 (SSH, key-only — password authentication is disabled), and ICMP. Port 8080 (/healthz) accepts traffic only from the load balancer health-check range. Egress is a full-tunnel internet gateway (TCP all ports + ICMP + UDP to the internet) so VPN clients can reach the internet through the tunnel. Restrict access at the security-group level if required.
After deployment: zero-touch setup
On first boot the bootstrap provisions the VPN automatically. Across the HA pair exactly one node (guarded by a MongoDB lock) enables the Pritunl REST API, writes the credentials to Lockbox, and creates the organization, the VPN server, the forward routes to your VPC subnets, and the first user; it sets the server’s Public Address to the VIP and suppresses Pritunl’s first-run setup wizard. The shared MongoDB propagates everything to both nodes, and the failover daemon keeps the server hot on both nodes, clears NAT on routes to private (RFC1918) destinations (clients keep their real VPN addresses, routed mode), lowers the ping timeouts (60/120 -> 20/40 for faster rehandshake), and keeps every host’s Public Address set to the VIP. To configure Pritunl by hand instead, deploy with pritunl-enable-provision=false.
What you do after deploying:
-
Get the credentials from the Lockbox secret named
<deployment>-<app-id>-api-credentials(deployment outputapi_creds_secret_id):yc lockbox payload get --id <api_creds_secret_id>It holds
ADMIN_USERNAME/ADMIN_PASSWORD(the web-UI login) andAPI_TOKEN/API_SECRET(the REST API). No SSH and nopritunl default-passwordis needed. -
Download the first user’s profile and connect. Open the web UI at
https://<web_public_ip>and log in withADMIN_USERNAME/ADMIN_PASSWORD(the certificate is self-signed until you point a DNS A record atweb_public_ipand set a Let’s Encrypt domain in Settings — HA-safe: the ACME token and the issued cert live in the shared MongoDB, so either node answers the challenge and both serve the trusted cert). Under Users, the first user (named after the local part ofadmin_email) already exists; download its profile and connect to the VIP.The auto-created user has no PIN, and the server has OTP disabled, so it authenticates with the certificate profile only. If your OpenVPN client asks for a username/password, leave the password blank. To require two-factor auth, enable PIN + OTP on the user in the web UI (supported in Pritunl OSS — the client then enters PIN+OTP as the connection password).
-
Attach the return route table to your subnets — the one networking step the product cannot do for you (see “Routing VPC workloads back to VPN clients” below).
-
(Optional) Add more users, set a Let’s Encrypt domain, enable PIN+OTP, or manage the server through the web UI or the REST API.
Pritunl REST API access
Provisioning enables Pritunl’s HMAC-authenticated REST API (Pritunl OSS does expose it) and stores API_TOKEN / API_SECRET in the api-credentials Lockbox secret. Sign each request with the headers Auth-Token, Auth-Timestamp, Auth-Nonce, Auth-Signature, where the signature is base64(HMAC_SHA256(API_SECRET, "API_TOKEN×tamp&nonce&METHOD&path")). Use it to automate users, profiles, and server changes against https://<web_public_ip>.
Routing VPC workloads back to VPN clients
VPN clients keep their pool addresses inside the VPC (no source NAT), so VPC resources need a return route. The product creates a route table whose client-pool route always points at the active node, but it does not attach the table to your subnets — associate it with every subnet that must reach VPN clients:
yc vpc subnet update <SUBNET_ID> --route-table-id <route_table_id>
Without this, packets from a VPC resource back to a VPN client are dropped. Because clients keep their real addresses, you can write security-group rules and audit traffic per VPN client address.
Before deleting the deployment, detach this route table from your subnets first — the deployment owns the route table and cannot delete it while it is still associated with a subnet (you attached it, so you detach it):
yc vpc subnet update <SUBNET_ID> --disassociate-route-table
Limitations
- OpenVPN over TCP only for now. WireGuard and the OpenVPN-over-UDP option are not offered yet: they ride the NLB’s UDP path, and UDP load-balancing on the Yandex NLB was intermittent in testing (a WireGuard tunnel handshaked but stopped passing return traffic on one deployment) and still needs a support ticket to enable. The product ships OpenVPN over TCP, which worked end-to-end in every test. WireGuard/UDP will be added once Yandex NLB UDP load-balancing is generally available.
- Failover convergence is ~20-60 seconds. The NLB health gate and the VPC route propagation are independent branches; both must complete. A graceful node stop adds its shutdown window (the leader holds the lease until the node actually stops); a hard node failure is at the lower end.
- No session preservation across failover. Clients rehandshake to the same VIP and keep their VPN address. OpenVPN usually reconnects within ~30-60s, but can take up to a few minutes after a graceful node stop, because the OpenVPN client’s reconnect backoff grows while convergence completes; a hard crash is faster, and lowering the client’s
connect-retrycap speeds it up. - No automatic failback. The recovered node stays passive until the active node fails; this is what prevents flapping.
- Zero-touch provisioning is opt-out. The organization, VPN server, and first user are created automatically on first boot, and Pritunl’s HMAC REST API is enabled (credentials in Lockbox). Set
pritunl-enable-provision=falseto skip provisioning and configure Pritunl manually in the web UI instead. - The client pool CIDR must not overlap any VPC subnet, or the route table entry cannot be created.
- The route table is not auto-attached to subnets — the operator associates it (see “Routing VPC workloads back to VPN clients”).
- Managed MongoDB is a paid resource billed independently of the VMs; the 3-host cluster is the largest cost item of the deployment.
- Single VPC network. The nodes have one NIC each and live in one VPC network, and the failover daemon writes a single next-hop (the active node’s internal IP) into the route tables. Yandex requires a route’s next hop to be in the same network as the route table, so VPN clients can reach resources in any subnet or availability zone of that one network, but not resources in a different VPC network. Yandex Cloud has no VPC peering, so place the resources your VPN clients must reach in the same VPC network as the deployment.
OpenNix provides technical support to Pritunl users in Yandex Cloud. You can contact their technical support by email at support@opennix.ru. Support engineers are available on business days from 9 am to 6 pm GMT+3.
| Resource type | Quantity |
|---|---|
| Service account | 1 |
| Access rights for folder | 2 |
| Lockbox secrets | 2 |
| Access binding for Lockbox secret | 1 |
| Lockbox secret version | 1 |
| MongoDB cluster | 1 |
| MongoDB database | 1 |
| MongoDB user | 1 |
| VPC IP addresses | 2 |
| VPC security group | 1 |
| VPC route table | 1 |
| NLB target group | 1 |
| Network Load Balancers | 2 |
| Virtual machines | 2 |