Перейти к содержимому

Настройка обратного прокси

Запуск 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-апгрейды без дополнительной настройки.

{
# 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-nginx
sudo certbot --nginx -d your-domain.com

Certbot автоматически добавит строки ssl_certificate в ваш конфиг. Примеры ниже включают их явно, чтобы полная конфигурация была понятна.

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 → HTTPS
server {
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 → HTTPS
server {
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 сессии не разделяется между экземплярами.

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 check

HAProxy ожидает сертификат и приватный ключ, объединённые в один PEM-файл:

Окно терминала
cat /etc/letsencrypt/live/your-domain.com/fullchain.pem \
/etc/letsencrypt/live/your-domain.com/privkey.pem \
> /etc/haproxy/certs/your-domain.pem
chmod 600 /etc/haproxy/certs/your-domain.pem

Добавьте к хуку обновления certbot:

/etc/letsencrypt/renewal-hooks/deploy/haproxy-reload.sh
#!/bin/bash
cat /etc/letsencrypt/live/your-domain.com/fullchain.pem \
/etc/letsencrypt/live/your-domain.com/privkey.pem \
> /etc/haproxy/certs/your-domain.pem
systemctl reload haproxy
Окно терминала
chmod +x /etc/letsencrypt/renewal-hooks/deploy/haproxy-reload.sh
Окно терминала
haproxy -c -f /etc/haproxy/haproxy.cfg # validate config
systemctl reload haproxy

rVPN устанавливает согласование ключей X3DH и сессию Double Ratchet для каждого соединения. Это состояние сессии живёт в памяти конкретного экземпляра сервера, обработавшего рукопожатие. Если переподключающийся клиент попадёт на другой бэкенд, рукопожатие придётся повторить — старая сессия теряется.

С ip_hash (Caddy) или balance source (HAProxy) клиенты стабильно попадают на один и тот же бэкенд при переподключениях. Если этот бэкенд упадёт, они автоматически заново пройдут рукопожатие на другом.