Skip to content

VPN Exit Controller - Technical Architecture

Overview

The VPN Exit Controller is a sophisticated system that manages dynamic country-based VPN exit nodes using Tailscale mesh networking, Docker containers, and intelligent load balancing. The system provides HTTP/HTTPS proxy services through country-specific subdomains, enabling users to route traffic through different geographical locations.

System Architecture Diagram

Internet → Cloudflare → Proxmox LXC → Traefik → HAProxy → VPN Exit Nodes
   ↓           ↓            ↓           ↓         ↓           ↓
 Users    DNS/CDN     Host System   SSL Term   Routing   Tailscale+VPN
                   (10.10.10.20)  (Port 443)  (Port 8080) (100.x.x.x)

Network Flow Detail

1. User Request: https://proxy-us.rbnk.uk
2. Cloudflare DNS Resolution: 135.181.60.45
3. Proxmox Host: 135.181.60.45:443
4. Traefik (LXC 201): SSL termination + routing
5. HAProxy: Country-based backend selection
6. VPN Exit Node: Docker container with NordVPN + Tailscale
7. Final destination via NordVPN servers

Core Components

1. FastAPI Application (/opt/vpn-exit-controller/api/)

The central orchestration service built with FastAPI that manages the entire VPN exit node ecosystem.

Key Features: - RESTful API for node management - Web-based dashboard with real-time status - Authentication using HTTP Basic Auth - Background services for monitoring and metrics

Structure:

api/
├── main.py              # FastAPI application entry point
├── routes/              # API route handlers
│   ├── nodes.py         # Node management endpoints
│   ├── proxy.py         # Proxy configuration endpoints
│   ├── load_balancer.py # Load balancing control
│   ├── metrics.py       # Metrics and monitoring
│   └── failover.py      # Failover management
└── services/            # Business logic services
    ├── docker_manager.py    # Docker container orchestration
    ├── proxy_manager.py     # HAProxy configuration management
    ├── load_balancer.py     # Intelligent node selection
    ├── redis_manager.py     # State and metrics storage
    └── metrics_collector.py # Real-time metrics collection

2. Docker-based VPN Exit Nodes

Each VPN node runs in a dedicated Docker container combining NordVPN and Tailscale.

Container Architecture:

FROM ubuntu:22.04
RUN apt-get install openvpn tailscale iptables
COPY entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

Node Lifecycle: 1. Container starts with country-specific environment variables 2. OpenVPN connects to optimal NordVPN server for the country 3. Tailscale connects to mesh network as exit node 4. IP forwarding rules enable traffic routing 5. Health monitoring ensures connectivity

Resource Limits: - Memory: 512MB per container - CPU: 50% of one core - Swap: 1GB total (memory + swap)

3. Traefik SSL Termination and Reverse Proxy

Traefik handles SSL certificate management and initial request routing.

Configuration: - SSL certificates via Let's Encrypt + Cloudflare DNS challenge - Automatic certificate renewal - Security headers middleware - Docker provider for service discovery

Key Features: - Wildcard SSL certificate for *.rbnk.uk - Automatic service discovery through Docker labels - Prometheus metrics export - Dashboard at traefik-vpn.rbnk.uk

4. HAProxy Country-based Routing System

HAProxy provides intelligent country-based request routing and load balancing.

Routing Logic:

Request: https://proxy-us.rbnk.uk/path
HAProxy ACL: hdr(host) -i proxy-us.rbnk.uk
Backend Selection: proxy_us
Server Selection: Load balancing among US nodes

Backend Configuration: - Round-robin load balancing per country - Health checks every 10 seconds - Automatic failover to backup servers - Dynamic configuration updates

Health Monitoring:

GET /health HTTP/1.1
Host: proxy-{country}.rbnk.uk
Expected: 200 OK

5. Redis Metrics and State Storage

Redis serves as the central data store for real-time metrics, connection tracking, and system state.

Data Structure:

node:{node_id}              # Node metadata and configuration
metrics:{node_id}:current   # Real-time node metrics
metrics:{node_id}:history   # Historical metrics (1 hour window)
connections:{node_id}       # Active connection counter
server_health:{server}      # VPN server health and latency

Metrics Tracked: - CPU usage percentage - Memory usage in MB - Network I/O statistics - VPN connection status - Tailscale connectivity - Active proxy connections

VPN Node Architecture

Container Design

Each VPN exit node is a self-contained Docker container that provides secure routing through a specific country with integrated proxy services.

┌─────────────────────────────────────────────────────────────┐
│                  VPN Exit Node Container                    │
├─────────────────────────────────────────────────────────────┤
│  ┌─────────────┐  ┌──────────────┐  ┌─────────────────────┐ │
│  │  OpenVPN    │  │  Tailscale   │  │   Proxy Services    │ │
│  │ (NordVPN)   │  │ (Exit Node)  │  │                     │ │
│  │             │  │              │  │ ┌─────────────────┐ │ │
│  │ Port: tun0  │  │ Port: ts0    │  │ │ Squid HTTP/S    │ │ │
│  └─────────────┘  └──────────────┘  │ │ Port: 3128      │ │ │
│           │               │         │ └─────────────────┘ │ │
│  ┌─────────────────────────────────┐ │ ┌─────────────────┐ │ │
│  │       iptables Routing          │ │ │ Dante SOCKS5    │ │ │
│  │   tun0 ←→ tailscale0           │ │ │ Port: 1080      │ │ │
│  └─────────────────────────────────┘ │ └─────────────────┘ │ │
│                                      │ ┌─────────────────┐ │ │
│  ┌─────────────────────────────────┐ │ │ Health Check    │ │ │
│  │       DNS Configuration         │ │ │ Port: 8080      │ │ │
│  │ NordVPN DNS: 103.86.96.100     │ │ └─────────────────┘ │ │
│  │ NordVPN DNS: 103.86.99.100     │ │                     │ │
│  │ Fallback: 8.8.8.8, 1.1.1.1    │ └─────────────────────┘ │
│  └─────────────────────────────────┘                       │
└─────────────────────────────────────────────────────────────┘

NordVPN Integration

Server Selection: - Country-specific server pools - Automatic optimal server selection based on latency - Support for both TCP and UDP configurations - Service credentials authentication

Configuration Management:

configs/vpn/
├── us.ovpn              # Default US configuration
├── us/                  # Specific US servers
│   ├── us5063.nordvpn.com.tcp.ovpn
│   └── us5064.nordvpn.com.udp.ovpn
└── auth.txt            # NordVPN service credentials

Tailscale Mesh Networking

Exit Node Configuration: - Advertises as exit node on Tailscale network with --advertise-exit-node - Uses --accept-dns=false to prevent DNS conflicts (fixes HTTPS errors in incognito mode) - Ephemeral auth key configuration for automatic device cleanup - Unique hostname: exit-{country}-{instance} - Userspace networking for container compatibility - Automatic IP assignment from 100.x.x.x range

Network Architecture:

Internet ←→ Tailscale Client ←→ Tailscale Mesh ←→ Exit Node ←→ NordVPN ←→ Destination
                                  (100.x.x.x)      (tun0)      (VPN Server)

DNS Resolution Configuration: To resolve HTTPS errors in incognito mode and improve reliability:

  1. Tailscale DNS Disabled: --accept-dns=false prevents Tailscale from overriding DNS
  2. NordVPN DNS Primary: Uses NordVPN's DNS servers (103.86.96.100, 103.86.99.100)
  3. Google DNS Fallback: Falls back to 8.8.8.8 and 1.1.1.1 if NordVPN DNS fails
  4. Container DNS Override: Manual /etc/resolv.conf configuration in containers

This configuration eliminates the "doesn't support secure connection" errors that occurred when using Tailscale's DNS resolution through the VPN tunnel.

Health Monitoring and Auto-Recovery

Health Checks: 1. Container status monitoring 2. VPN tunnel connectivity (ip route | grep tun0) 3. Tailscale connection status 4. Exit node advertisement verification

Auto-Recovery Process: 1. Health check failure detected 2. Container restart attempted (max 3 times) 3. If restart fails, node marked unhealthy 4. Load balancer redirects traffic to healthy nodes 5. Failed node removed after timeout

Proxy Routing System

Multi-Protocol Proxy Chain

The system provides a comprehensive proxy chain supporting HTTP/HTTPS and SOCKS5 protocols:

Client → HAProxy → Tailscale Mesh → VPN Container → Internet
  ↓        ↓           ↓               ↓              ↓
Request  Routing  Mesh Network    Proxy Services   Destination
         Layer    (100.x.x.x)    (Squid/Dante)    (via NordVPN)

Proxy Chain Components: 1. HAProxy: L7 load balancer with ACL-based country routing 2. Tailscale Mesh: Secure encrypted tunnel network (100.64.0.0/10) 3. VPN Container: Integrated Squid (HTTP/HTTPS) and Dante (SOCKS5) proxies 4. NordVPN: Exit point to internet with country-specific IP addresses

Country-based Subdomain Routing

The system uses DNS subdomains to route traffic through specific countries with multiple proxy protocols:

proxy-us.rbnk.uk    → United States exit nodes
proxy-uk.rbnk.uk    → United Kingdom exit nodes
proxy-de.rbnk.uk    → Germany exit nodes
proxy-jp.rbnk.uk    → Japan exit nodes

Available Proxy Protocols: - HTTP Proxy: http://<tailscale-ip>:3128 (Squid) - SOCKS5 Proxy: socks5://<tailscale-ip>:1080 (Dante) - Health Check: http://<tailscale-ip>:8080/health

HAProxy ACL-Based Routing (Updated)

HAProxy now uses ACL-based routing instead of regex for better performance and reliability:

# Country-specific ACLs using hostname matching
acl is_us_proxy hdr(host) -i proxy-us.rbnk.uk
acl is_uk_proxy hdr(host) -i proxy-uk.rbnk.uk
acl is_de_proxy hdr(host) -i proxy-de.rbnk.uk

# Route to appropriate backend
use_backend proxy_us if is_us_proxy
use_backend proxy_uk if is_uk_proxy
use_backend proxy_de if is_de_proxy

Backend Server Selection with Health Checks

For each country backend, HAProxy selects from available healthy nodes using HTTP health checks:

backend proxy_us
    mode http
    balance roundrobin
    option httpchk GET /health HTTP/1.1\r\nHost:\ localhost
    http-check expect status 200

    # VPN container nodes with health checks
    server us-node-1 100.86.140.98:3128 check inter 10s
    server us-node-2 100.86.140.99:3128 check inter 10s
    server us-backup 127.0.0.1:3128 backup

Health Check Updates for HAProxy 2.8: - Updated health check syntax for compatibility - HTTP health checks on port 8080 (/health endpoint) - 10-second check intervals with automatic failover

Load Balancing System

5 Load Balancing Strategies

  1. Round Robin: Sequential distribution across nodes
  2. Least Connections: Route to node with fewest active connections
  3. Weighted Latency: Prefer nodes with lower VPN server latency
  4. Random: Random node selection
  5. Health Score: Comprehensive scoring based on multiple factors

Health Score Calculation

The health score algorithm considers multiple factors:

def calculate_health_score(node):
    score = 100.0  # Perfect score baseline

    # Server latency (40% weight)
    latency_score = max(50, 100 - (latency - 50) * 0.5)
    score = score * 0.6 + latency_score * 0.4

    # Connection count (30% weight)
    connection_penalty = min(20, connection_count * 2)
    connection_score = max(60, 100 - connection_penalty)
    score = score * 0.7 + connection_score * 0.3

    # CPU usage (20% weight)
    cpu_score = max(60, 100 - cpu_percent)
    score = score * 0.8 + cpu_score * 0.2

    # Memory usage (10% weight)
    memory_penalty = max(0, (memory_mb - 300) / 10)
    memory_score = max(70, 100 - memory_penalty)
    score = score * 0.9 + memory_score * 0.1

    return score

Automatic Scaling Logic

Scale Up Conditions: - Average connections per node > 50 - Current node count < 3 for the country - At least one healthy server available

Scale Down Conditions: - Average connections per node < 10 - Current node count > 1 for the country - Target node has 0 active connections

Infrastructure Details

Proxmox LXC Container Setup

Container Configuration: - Container ID: 201 - OS: Ubuntu 22.04 - Internal IP: 10.10.10.20 - Public IP: 135.181.60.45 - Memory: 8GB - Storage: 100GB

Special Permissions Required:

pct set 201 -features nesting=1,keyctl=1
pct set 201 -lxc.apparmor.profile: unconfined

Network Configuration

Network Stack:

┌─────────────────────────────────────┐
│        Internet (135.181.60.45)     │
├─────────────────────────────────────┤
│         Proxmox Host                │
│    ┌─────────────────────────────┐   │
│    │    LXC Container 201        │   │
│    │    IP: 10.10.10.20         │   │
│    │  ┌───────────────────────┐  │   │
│    │  │   Docker Network      │  │   │
│    │  │  traefik_proxy        │  │   │
│    │  │  vpn_network          │  │   │
│    │  └───────────────────────┘  │   │
│    └─────────────────────────────┘   │
└─────────────────────────────────────┘

Port Mapping: - 80 → Traefik HTTP - 443 → Traefik HTTPS - 8080 → FastAPI Application - 8081 → Traefik Dashboard - 8404 → HAProxy Stats

DNS Configuration with Cloudflare

DNS Records:

A     rbnk.uk                    135.181.60.45
A     *.rbnk.uk                  135.181.60.45
CNAME proxy-us.rbnk.uk          rbnk.uk
CNAME proxy-uk.rbnk.uk          rbnk.uk
CNAME proxy-de.rbnk.uk          rbnk.uk

Cloudflare Settings: - Proxy enabled for DDoS protection - SSL/TLS: Full (strict) - Always Use HTTPS: On - HSTS enabled

Configuration Examples

Docker Compose for API Services

version: '3.8'
services:
  api:
    build: ./api
    container_name: vpn-api
    network_mode: host
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./configs:/configs
    environment:
      - TAILSCALE_AUTHKEY=${TAILSCALE_AUTHKEY}
    restart: unless-stopped

  redis:
    image: redis:7-alpine
    container_name: vpn-redis
    network_mode: host
    volumes:
      - redis-data:/data
    restart: unless-stopped

Traefik Configuration

# traefik.yml
entryPoints:
  web:
    address: ":80"
  websecure:
    address: ":443"

certificatesResolvers:
  cf:
    acme:
      email: "[email protected]"
      storage: /letsencrypt/acme.json
      dnsChallenge:
        provider: cloudflare

Environment Variables

# Required environment variables
TAILSCALE_AUTHKEY=tskey-auth-xxxxx     # Tailscale auth key
ADMIN_USER=admin                       # API admin username
ADMIN_PASS=Bl4ckMagic!2345erver       # API admin password
SECRET_KEY=your-secret-key             # FastAPI secret key
REDIS_URL=redis://localhost:6379       # Redis connection string
CF_DNS_API_TOKEN=cloudflare-token      # Cloudflare API token

Monitoring and Observability

Metrics Collection

System Metrics: - Node count per country - Connection distribution - CPU and memory usage - Network throughput - VPN connection stability

Business Metrics: - Request success rate - Response time percentiles - Geographic usage distribution - Load balancing effectiveness

Health Monitoring

Health Check Endpoints: - /health - API service health - /api/nodes - Node status overview - /api/metrics - System metrics - HAProxy stats at :8404/stats - Traefik dashboard at :8081

Alerting and Failover

Automatic Failover Triggers: - Node health check failures - High CPU/memory usage - VPN connection loss - Tailscale connectivity issues

Recovery Actions: - Container restart (up to 3 attempts) - Node replacement with fresh container - Load balancer traffic redirection - Administrative notifications

Security Considerations

Network Isolation

  • Each VPN node runs in isolated Docker container
  • Network policies restrict inter-container communication
  • VPN credentials stored securely in mounted volumes

Authentication and Authorization

  • HTTP Basic Auth for API access
  • Tailscale authentication for mesh network
  • NordVPN service credentials for VPN access

SSL/TLS Configuration

  • End-to-end encryption via Traefik
  • Let's Encrypt certificates with automatic renewal
  • Secure headers middleware
  • HSTS enforcement

Deployment and Operations

Initial Setup

  1. Proxmox LXC Creation:

    pct create 201 ubuntu-22.04-standard_22.04-1_amd64.tar.xz \
      --hostname vpn-controller \
      --memory 8192 \
      --rootfs local-lvm:100
    

  2. Container Permissions:

    pct set 201 -features nesting=1,keyctl=1
    pct set 201 -lxc.apparmor.profile: unconfined
    

  3. Service Installation:

    cd /opt/vpn-exit-controller
    ./setup-project.sh
    systemctl enable vpn-controller
    systemctl start vpn-controller
    

Maintenance Operations

Health Monitoring:

# Check service status
systemctl status vpn-controller

# View real-time logs
journalctl -u vpn-controller -f

# Check Docker containers
docker ps -a --filter label=vpn.exit-node=true

Configuration Updates:

# Update HAProxy configuration
curl -X POST http://localhost:8080/api/proxy/update-config

# Restart all nodes for a country
curl -X POST http://localhost:8080/api/nodes/us/restart-all

Backup and Recovery:

# Backup Redis data
docker exec vpn-redis redis-cli BGSAVE

# Backup configuration
tar -czf backup.tar.gz /opt/vpn-exit-controller/configs

This architecture provides a robust, scalable, and intelligent VPN exit node system that automatically manages geographic traffic routing while maintaining high availability and performance.