跳转至

反向代理设置

在反向代理后面运行 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 在代理层终止

代理处理 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 连接必需
    servers {
        timeouts {
            idle 24h
        }
        keepalive_idle 1m
        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
            }
            flush_interval -1    # WebSocket 必需——禁用响应缓冲
        }
    }
}

带诱饵网站

在根路径提供正常外观的网站。只有 /api/* 被转发到 R-VPN。随意访问者和自动化扫描器看到的是一个常规网站。

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 自动为 your-domain.com 配置 Let's Encrypt 证书。无需手动 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
                keepalive 0s
            }
            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 将在 60 秒(默认值)后关闭空闲的 WebSocket 连接。


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 哈希 — ratchet 状态需要粘性会话
    option  forwardfor
    option  http-server-close

    # 健康检查:rvpn-server 在 GET /healthz 时返回 200
    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

HAProxy 的 TLS 证书

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 粘性负载均衡

R-VPN 每个连接建立一次 X3DH 密钥协商和 Double Ratchet 会话。此会话状态存在于处理握手的特定服务器实例的内存中。如果重新连接的客户端被路由到不同的后端,握手必须重复——旧会话丢失。

使用 ip_hash(Caddy)或 balance source(HAProxy),客户端在重新连接时始终到达同一后端。如果该后端宕机,它们将自动在另一个后端重新握手。