Перейти к содержанию

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

Запуск 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 после каждого обновления. Ручной хук обновления не требуется.

sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d your-domain.com

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
chmod +x /etc/letsencrypt/renewal-hooks/deploy/haproxy-reload.sh

Применение изменений

haproxy -c -f /etc/haproxy/haproxy.cfg   # проверка конфигурации
systemctl reload haproxy

Почему IP-sticky балансировка нагрузки

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

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