反向代理设置
将 rVPN 运行在反向代理后可以在代理层终止 TLS,在根路径提供诱饵网站,并在多个服务器实例之间分发连接。代理仅将 /api/* 流量转发给 rVPN——其他一切都可以提供看起来正常的网站。
在反向代理后运行时,请省略 TLS 证书并绑定到本地端口:
[server]bind_address = "127.0.0.1:8443"websocket_path = "/api/v1/ws"identity_key_file = "/etc/rvpn/server_identity.key"# No tls_cert_file / tls_key_file — TLS is terminated at the proxy代理处理 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)
Section titled “简单(仅 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 } }}在根路径提供看起来正常的网站。只有 /api/* 会被转发到 rVPN。普通访客和自动扫描器看到的是一个常规网站。
{ # 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 会自动为
your-domain.com申请 Let’s Encrypt 证书。无需手动运行certbot。
多服务器(带负载均衡)
Section titled “多服务器(带负载均衡)”{ 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)
Section titled “简单(仅 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 会在 60 秒(默认)后关闭空闲的 WebSocket 连接。
HAProxy(多服务器负载均衡)
Section titled “HAProxy(多服务器负载均衡)”HAProxy 非常适合在多个 rVPN 服务器实例之间分发连接。请使用 balance source(IP 哈希)以确保每个客户端始终落到同一后端——由于会话 ratchet 状态不在实例间共享,这是必需的。
/etc/haproxy/haproxy.cfg
Section titled “/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 checkHAProxy 的 TLS 证书
Section titled “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.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.shhaproxy -c -f /etc/haproxy/haproxy.cfg # validate configsystemctl reload haproxy为什么需要 IP 粘性的负载均衡
Section titled “为什么需要 IP 粘性的负载均衡”rVPN 为每个连接建立 X3DH 密钥协商与 Double Ratchet 会话。这些会话状态存储在处理握手的具体服务器实例的内存中。如果重新连接的客户端被路由到不同的后端,握手必须重新进行——旧会话已经丢失。
使用 ip_hash(Caddy)或 balance source(HAProxy),客户端在重连间会一致地到达同一后端。如果该后端宕机,它们会自动在另一个后端上重新握手。