Настройка обратного прокси¶
Запуск r-vpn за обратным прокси позволяет терминировать TLS на уровне прокси, обслуживать сайт-прикрытие на корневом пути и распределять подключения между несколькими экземплярами сервера. Прокси пересылает только трафик /api/* на r-vpn -- всё остальное может обслуживать обычный сайт.
Конфигурация сервера¶
При запуске за прокси не указывайте 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 terminates на прокси
Прокси обрабатывает TLS и пересылает все запросы /api/* на 127.0.0.1:8443. Это покрывает основной WebSocket-эндпоинт (/api/v1/ws), TUN-эндпоинт (/api/v1/ws/tun) и DNS-эндпоинт (/api/v1/ws/dns).
Caddy¶
Caddy -- рекомендуемый прокси: он автоматически получает и обновляет TLS-сертификаты и обрабатывает WebSocket-апгрейды без дополнительной настройки.
Простая конфигурация (только VPN)¶
{
# Глобальные параметры сервера -- обязательны для долгоживущих WebSocket-подключений.
# Не влияют на обычный HTTP-трафик, только на поведение простаивающих подключений.
servers {
timeouts {
idle 24h # WebSocket-подключения долгоживущие
}
keepalive_idle 1m # Начать TCP keepalive-пробы после 1м простоя
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 # без переиспользования upstream-подключений
}
flush_interval -1 # критично для WebSocket -- отключает буферизацию ответов
}
}
}
С сайтом-прикрытием¶
Обслуживайте обычный сайт на корневом пути. Только /api/* пересылается на r-vpn. Случайные посетители и автоматические сканеры видят обычный сайт.
{
# Глобальные параметры сервера -- обязательны для долгоживущих WebSocket-подключений.
servers {
timeouts {
idle 24h
}
keepalive_idle 1m
keepalive_interval 30s
keepalive_count 5
}
}
your-domain.com {
tls {
protocols tls1.3
}
# VPN-трафик -- все подпути /api/ направляются на rvpn-server
handle /api/* {
reverse_proxy 127.0.0.1:8443 {
transport http {
read_timeout 0
write_timeout 0
keepalive 0s
}
flush_interval -1 # критично для WebSocket -- отключает буферизацию ответов
}
}
# Сайт-прикрытие -- всё остальное обслуживает статические файлы
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 # привязка по IP клиента -- необходима для сессионного состояния ratchet
health_uri /healthz
health_interval 10s
transport http {
read_timeout 0
write_timeout 0
}
flush_interval -1 # критично для WebSocket -- отключает буферизацию ответов
}
}
handle {
root * /var/www/html
file_server
}
}
lb_policy ip_hash важен: сессия X3DH + Double Ratchet каждого клиента привязана к конкретному экземпляру сервера, поэтому подключения с одного IP клиента всегда должны попадать на один и тот же бэкенд.
Nginx¶
Nginx не получает сертификаты самостоятельно, но плагин certbot --nginx обрабатывает как первоначальную выдачу, так и автоматическое обновление -- он редактирует конфигурацию, добавляя пути к сертификатам, и его systemd-таймер перезагружает nginx после каждого обновления. Ручной хук обновления не требуется.
Certbot автоматически добавит строки ssl_certificate в вашу конфигурацию. Примеры ниже включают их явно для полноты.
Простая конфигурация (только 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;
# Проксирование всех /api/* на rvpn-server.
# Префиксное сопоставление покрывает /api/v1/ws, /api/v1/ws/tun и /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; # без тайм-аута -- VPN-подключения долгоживущие
}
}
# Перенаправление 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 -- все пути /api/ пересылаются на 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;
}
# Сайт-прикрытие -- статические файлы для всего остального
root /var/www/html;
location / {
try_files $uri $uri/ =404;
}
}
# Перенаправление 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 хорошо подходит для распределения подключений между несколькими экземплярами сервера r-vpn. Используйте balance source (IP-хеш), чтобы каждый клиент всегда попадал на один и тот же бэкенд -- это необходимо, поскольку состояние сессионного ratchet не разделяется между экземплярами.
/etc/haproxy/haproxy.cfg¶
global
log /dev/log local0
maxconn 50000
ssl-default-bind-options ssl-min-ver TLSv1.3 # Только TLS 1.3
defaults
log global
mode http
option httplog
timeout connect 10s
timeout client 30s
timeout server 30s
timeout tunnel 1h # WebSocket-подключения -- должны быть долгоживущими
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 }
# Маршрутизация /api/* в кластер rvpn
acl is_vpn path_beg /api/
use_backend rvpn_cluster if is_vpn
# Всё остальное -> сайт-прикрытие
default_backend decoy_site
backend rvpn_cluster
balance source # IP-хеш -- sticky-сессии необходимы для состояния ratchet
option forwardfor
option http-server-close
# Проверка работоспособности: rvpn-server возвращает 200 на GET /healthz
option httpchk GET /healthz
http-check expect status 200
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
server web 127.0.0.1:80 check
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.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
Применение изменений¶
Почему IP-sticky балансировка нагрузки¶
r-vpn устанавливает соглашение ключей X3DH и сессию Double Ratchet для каждого подключения. Это сессионное состояние хранится в памяти конкретного экземпляра сервера, обработавшего руковопожатие. Если переподключающийся клиент маршрутизируется на другой бэкенд, руковопожатие должно повториться -- старая сессия потеряна.
С ip_hash (Caddy) или balance source (HAProxy) клиенты стабильно попадают на один и тот же бэкенд при переподключениях. Если этот бэкенд выходит из строя, клиенты автоматически выполнят повторное руковопожатие на другом.