Настройка обратного прокси
Запуск rVPN за обратным прокси позволяет терминировать TLS на уровне прокси, обслуживать сайт-приманку в корне и распределять соединения между несколькими экземплярами сервера. Прокси пересылает на rVPN только трафик /api/* — всё остальное может обслуживать обычный на вид веб-сайт.
Конфигурация сервера
Заголовок раздела «Конфигурация сервера»При работе за прокси опустите TLS-сертификат и привяжите к локальному порту:
[server]bind_address = "127.0.0.1:8443"websocket_path = "/api/v1/ws"identity_key_file = "/etc/rvpn/server_identity.key"# Нет tls_cert_file / tls_key_file — TLS терминируется на проксиПрокси обрабатывает TLS и пересылает все запросы /api/* на 127.0.0.1:8443. Это охватывает основную WebSocket-точку (/api/v1/ws), точку TUN (/api/v1/ws/tun) и точку DNS (/api/v1/ws/dns).
Caddy — рекомендуемый прокси: он автоматически получает и обновляет TLS-сертификаты и обрабатывает WebSocket-апгрейды без дополнительной настройки.
Простой (только VPN)
Заголовок раздела «Простой (только VPN)»{ # Global server options — required for long-lived WebSocket connections. # These don't affect normal HTTP traffic, only idle connection behavior. servers { timeouts { idle 24h # WebSocket connections are long-lived } keepalive_idle 1m # Start TCP keepalive probes after 1m idle keepalive_interval 30s keepalive_count 5 }}
your-domain.com { tls { protocols tls1.3 }
handle /api/* { reverse_proxy 127.0.0.1:8443 { transport http { read_timeout 0 write_timeout 0 keepalive 0s # no upstream connection reuse } flush_interval -1 # critical for WebSocket — disables response buffering } }}С сайтом-приманкой
Заголовок раздела «С сайтом-приманкой»Обслуживайте обычный на вид сайт в корне. На rVPN пересылается только /api/*. Случайные посетители и автоматические сканеры видят обычный сайт.
{ # Global server options — required for long-lived WebSocket connections. servers { timeouts { idle 24h } keepalive_idle 1m keepalive_interval 30s keepalive_count 5 }}
your-domain.com { tls { protocols tls1.3 }
# VPN traffic — all sub-paths under /api/ go to rvpn-server handle /api/* { reverse_proxy 127.0.0.1:8443 { transport http { read_timeout 0 write_timeout 0 keepalive 0s } flush_interval -1 # critical for WebSocket — disables response buffering } }
# Decoy site — everything else serves static files handle { root * /var/www/html file_server }}Разместите файлы сайта-приманки в /var/www/html. Подойдёт любой рабочий статический HTML-сайт — блог, лендинг, портфолио. Чем реалистичнее он выглядит, тем лучше.
Совет: Caddy автоматически получает сертификат Let’s Encrypt для
your-domain.com. Ручной запускcertbotне требуется.
Несколько серверов (с балансировкой нагрузки)
Заголовок раздела «Несколько серверов (с балансировкой нагрузки)»{ servers { idle_timeout 24h keepalive_idle 1m keepalive_interval 30s keepalive_count 5 }}
your-domain.com { tls { protocols tls1.3 }
handle /api/* { reverse_proxy 10.0.0.1:8443 10.0.0.2:8443 10.0.0.3:8443 { lb_policy ip_hash # sticky by client IP — required for per-session ratchet state # rvpn-server has no /healthz endpoint. Use passive health checks: # a backend is marked down after unresponsiveness during a real request. fail_duration 30s max_fails 3
transport http { read_timeout 0 write_timeout 0 } flush_interval -1 # critical for WebSocket — disables response buffering } }
handle { root * /var/www/html file_server }}lb_policy ip_hash важен: сессия X3DH + Double Ratchet каждого клиента привязана к конкретному экземпляру сервера, поэтому соединения от одного и того же клиентского IP должны всегда попадать на один и тот же бэкенд.
Nginx сам не выдаёт сертификаты, но плагин certbot --nginx обрабатывает как первоначальную выдачу, так и автоматическое обновление — он редактирует ваш конфиг, добавляя пути к сертификатам, а его systemd-таймер перезагружает nginx после каждого обновления. Ручной хук обновления не нужен.
sudo apt install certbot python3-certbot-nginxsudo certbot --nginx -d your-domain.comCertbot автоматически добавит строки ssl_certificate в ваш конфиг. Примеры ниже включают их явно, чтобы полная конфигурация была понятна.
Простой (только VPN)
Заголовок раздела «Простой (только VPN)»server { listen 443 ssl; server_name your-domain.com;
ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem; ssl_protocols TLSv1.3;
# Proxy all /api/* to rvpn-server. # Prefix match covers /api/v1/ws, /api/v1/ws/tun, and /api/v1/ws/dns. location /api/ { proxy_pass http://127.0.0.1:8443; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_read_timeout 0; # never time out — VPN connections are long-lived }}
# Redirect HTTP → HTTPSserver { listen 80; server_name your-domain.com; return 301 https://$host$request_uri;}С сайтом-приманкой
Заголовок раздела «С сайтом-приманкой»server { listen 443 ssl; server_name your-domain.com;
ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem; ssl_protocols TLSv1.3;
# VPN — all paths under /api/ forwarded to rvpn-server location /api/ { proxy_pass http://127.0.0.1:8443; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_read_timeout 0; }
# Decoy site — static files for everything else root /var/www/html; location / { try_files $uri $uri/ =404; }}
# Redirect HTTP → HTTPSserver { listen 80; server_name your-domain.com; return 301 https://$host$request_uri;}Замечание:
proxy_read_timeout 0отключает таймаут чтения nginx для проксируемого соединения. Без этого nginx закроет простаивающие WebSocket-соединения через 60 секунд (значение по умолчанию).
HAProxy (балансировка между несколькими серверами)
Заголовок раздела «HAProxy (балансировка между несколькими серверами)»HAProxy хорошо подходит для распределения соединений между несколькими экземплярами сервера rVPN. Используйте balance source (IP hash), чтобы каждый клиент всегда попадал на один и тот же бэкенд — это необходимо, поскольку состояние ratchet сессии не разделяется между экземплярами.
/etc/haproxy/haproxy.cfg
Заголовок раздела «/etc/haproxy/haproxy.cfg»global log /dev/log local0 maxconn 50000 ssl-default-bind-options ssl-min-ver TLSv1.3 # TLS 1.3 only
defaults log global mode http option httplog timeout connect 10s timeout client 30s timeout server 30s timeout tunnel 1h # WebSocket connections — must be long
frontend https bind *:443 ssl crt /etc/haproxy/certs/your-domain.pem alpn h2,http/1.1 ssl-min-ver TLSv1.3 bind *:80 http-request redirect scheme https unless { ssl_fc }
# Route /api/* to the rvpn cluster acl is_vpn path_beg /api/ use_backend rvpn_cluster if is_vpn
# Everything else → decoy site default_backend decoy_site
backend rvpn_cluster balance source # IP hash — sticky sessions required for ratchet state option forwardfor option http-server-close
# rvpn-server has no /healthz endpoint. Fall back to plain L4 checks — # HAProxy will mark a backend down after `fall` failed TCP connects. server rvpn1 10.0.0.1:8443 check inter 10s rise 2 fall 3 server rvpn2 10.0.0.2:8443 check inter 10s rise 2 fall 3 server rvpn3 10.0.0.3:8443 check inter 10s rise 2 fall 3
backend decoy_site # Point at a separate host or a distinct port. If http_port = 80 is set in # server.toml AND rvpn-server runs on the same box, sending here loops back # into the built-in 404 page. server web 127.0.0.1:8080 checkTLS-сертификат для HAProxy
Заголовок раздела «TLS-сертификат для HAProxy»HAProxy ожидает сертификат и приватный ключ, объединённые в один PEM-файл:
cat /etc/letsencrypt/live/your-domain.com/fullchain.pem \ /etc/letsencrypt/live/your-domain.com/privkey.pem \ > /etc/haproxy/certs/your-domain.pemchmod 600 /etc/haproxy/certs/your-domain.pemДобавьте к хуку обновления certbot:
#!/bin/bashcat /etc/letsencrypt/live/your-domain.com/fullchain.pem \ /etc/letsencrypt/live/your-domain.com/privkey.pem \ > /etc/haproxy/certs/your-domain.pemsystemctl reload haproxychmod +x /etc/letsencrypt/renewal-hooks/deploy/haproxy-reload.shПрименение изменений
Заголовок раздела «Применение изменений»haproxy -c -f /etc/haproxy/haproxy.cfg # validate configsystemctl reload haproxyПочему нужна IP-«липкая» балансировка
Заголовок раздела «Почему нужна IP-«липкая» балансировка»rVPN устанавливает согласование ключей X3DH и сессию Double Ratchet для каждого соединения. Это состояние сессии живёт в памяти конкретного экземпляра сервера, обработавшего рукопожатие. Если переподключающийся клиент попадёт на другой бэкенд, рукопожатие придётся повторить — старая сессия теряется.
С ip_hash (Caddy) или balance source (HAProxy) клиенты стабильно попадают на один и тот же бэкенд при переподключениях. Если этот бэкенд упадёт, они автоматически заново пройдут рукопожатие на другом.