服务器配置参考
server.toml 所有字段的完整参考。
所有字段位于 [server] 段之下,或位于根级(两种都被接受)。
| 字段 | 类型 | 默认值 | 描述 |
|---|---|---|---|
bind_address | string | "0.0.0.0:443" | 监听的地址和端口。使用 "0.0.0.0:443" 监听所有接口,或使用 "127.0.0.1:8443" 仅监听本机(用于反向代理后运行)。 |
tls_cert_file | path | "certs/cert.pem" | TLS 证书路径(PEM 格式)。 |
tls_key_file | path | "certs/key.pem" | TLS 私钥路径(PEM 格式)。 |
identity_key_file | path | "server_identity.key" | 服务器 X3DH 身份密钥的路径。通过 rvpn-server keygen 生成。 |
websocket_path | string | "/api/v1/ws" | WebSocket 端点路径。为避免被检测,请为每次部署更改。 |
http_port | integer | (disabled) | 若设置,则同时在该端口监听明文 HTTP(例如 80),用于 ACME 校验和 HTTP→HTTPS 重定向。 |
redirect_http_to_https | bool | true | 当设置了 http_port 时,将所有 HTTP 请求重定向到 HTTPS。非 WebSocket 请求会收到硬编码的类 nginx 404 以伪装成普通 Web 服务器。 |
prekey_bundle_file | path | (none) | 若设置,则从此文件加载 X3DH 密钥而非生成新密钥。用于恢复已备份的身份。 |
[server.rate_limit]
Section titled “[server.rate_limit]”控制单个 IP 地址可建立的连接数。
| 字段 | 类型 | 默认值 | 描述 |
|---|---|---|---|
max_connections_per_ip | integer | 500 | 单个 IP 地址的最大并发连接数。 |
max_handshakes_per_minute | integer | 2000 | 单个 IP 每分钟允许的新握手尝试次数。有助于防止暴力探测。 |
[server.rate_limit]max_connections_per_ip = 10max_handshakes_per_minute = 20[server.network]
Section titled “[server.network]”仅用于启动警告的预检提示——服务器不会根据本段编写 NAT 或分配 IP。实际的客户端 IP 来自 [server.tun].tun_ip;NAT 由运维人员设置(参见 服务器安装 中的 iptables/pf 示例)。
| 字段 | 类型 | 默认值 | 描述 |
|---|---|---|---|
nat_enabled | bool | true | 若你的出口在别处处理,可设为 false 以抑制”客户端可能无法访问互联网”的启动警告。 |
dhcp_range | string | "10.200.0.0/24" | 服务器期望 NAT MASQUERADE 规则覆盖的子网。启动时服务器会在 iptables-save 中匹配此子网的规则,若未找到则发出警告。 |
[server.network]nat_enabled = truedhcp_range = "10.200.0.0/24"[server.tun]
Section titled “[server.tun]”真正的 TUN 到 TUN 模式设置。启用时,服务器创建 TUN 接口并通过它路由客户端数据包。
| 字段 | 类型 | 默认值 | 描述 |
|---|---|---|---|
enabled | bool | false | 启用 TUN 模式。为 false 时,服务器使用中继模式(Brook 风格)。 |
tun_ip | string | "10.200.0.1/24" | TUN 接口 IP 地址和子网前缀。客户端 IP 从此子网中分配。 |
mtu | integer | 1420 | TUN 接口的 MTU。 |
interface_name | string | "tun0" | 要创建的 TUN 接口名。 |
dns_servers | list | ["8.8.8.8"] | 通过隧道推送给 TUN 客户端的 DNS 解析器。 |
[server.tun]enabled = truetun_ip = "10.200.0.1/24"mtu = 1420interface_name = "tun0"[server.acme]
Section titled “[server.acme]”通过 Let’s Encrypt 自动获取 TLS 证书 — 不需要反向代理。服务器使用 TLS-ALPN-01 挑战自行获取并续订证书,因此同一个 :443 监听器同时处理正常流量和 ACME 握手。不需要 :80 端口。
| 字段 | 类型 | 默认值 | 描述 |
|---|---|---|---|
enabled | bool | false | 启用 ACME 签发。当 true 时,tls_cert_file 不能存在于磁盘上 — 服务器拒绝猜测该提供哪个证书。 |
domains | list of strings | [] | 要签发证书的完全限定域名。启用时必须至少有一个。第一个是主 CN;其余作为 SAN 附加。 |
contacts | list of strings | [] | ACME 账户的联系 URI,通常是 ["mailto:[email protected]"]。如果续订停滞,Let’s Encrypt 会通过这些地址发送到期警告。 |
cache_dir | path | "acme-cache" | 持久保存 ACME 账户密钥和已签发证书的目录。必须可写。跨重启保留以避免触发速率限制。 |
staging | bool | false | 使用 Let’s Encrypt 的 staging 目录。证书不会被浏览器信任,但签发不计入生产速率限制。在测试 DNS 和防火墙设置时使用。 |
前置条件 — 在设置 enabled = true 之前请确认:
domains中的每个主机名必须解析(A/AAAA)到本服务器的公网 IP。- 入站 TCP
:443必须能被 Let’s Encrypt 的验证器访问。如果在 NAT 网关后面,请转发端口。 cache_dir必须可写且持久 — 丢失它会强制重新签发并消耗速率限制预算。
[server]bind_address = "0.0.0.0:443"# tls_cert_file / tls_key_file 已省略 — ACME 提供证书。
[server.acme]enabled = truedomains = ["hk.example.com"]cache_dir = "/var/lib/rvpn/acme"首次启动会有 ~5–15 秒的延迟,期间 ACME 握手完成。续订在到期前约 30 天自动进行;驱动任务以 info 级别记录每个事件(ACME event: …)。
如果 Let’s Encrypt 无法访问(网络故障、LE 临时中断),驱动任务记录错误并重试 — 一次失败的续订不会拆除正在运行的监听器。如果证书停止轮换,请留意日志中反复出现的 ACME error: 行。
续订完全自动,无需操作员介入。
- 触发 — 驱动任务监控证书的
notAfter,并在到期前约 30 天开始新的订单。 - 热切换 — 新证书替换内存解析器中的旧证书。现有 TLS 连接保留建立时的证书;新握手拿到新证书。无重启,无连接中断。
- 持久化 — 新证书在切换前写入
cache_dir,因此在续订窗口中间重启会加载已签发的证书,而不是触发新订单。 - 无外部定时器 — 没有类似
certbot.timer的东西。整个状态机都在 rvpn-server 进程内。
要确认续订正常触发(首次签发后约 60 天),请查看 journal:
sudo journalctl -u rvpn-server --since "60d ago" | grep "ACME event"应该能看到最初的 NewAccount → DeployedNewCert → CertCacheStore 链,以及续订的第二对 DeployedNewCert / CertCacheStore。
Let’s Encrypt 速率限制
Section titled “Let’s Encrypt 速率限制”ACME 最痛的失败模式不是代码,而是 Let’s Encrypt 的速率限制。此集成涉及三个:
| 限制 | 触发条件 | 锁定时长 |
|---|---|---|
| Failed authorizations(失败授权) | 每主机每小时 5 次失败的 TLS-ALPN-01 挑战。配置错误的防火墙、DNS 或 ALPN 分派器会在几分钟内耗尽额度。 | 从最后一次失败起 60 分钟 |
| Duplicate certificates(重复证书) | 对同一组主机名每周 5 次成功签发。清空 cache_dir 后反复重新签发会触发。 | 从第 5 次签发起 168 小时 |
| New orders(新订单) | 每账户每 3 小时 300 个。仅在驱动任务对大量域名进入紧密重试循环时才相关。 | 3 小时 |
详情见 Let’s Encrypt rate limits。
当 rvpn-server 看到 LE 返回 429 时,会把日志从 ERROR 降为 WARN,并打印重试时间,让运维知道何时再看:
WARN ACME rate-limited by Let's Encrypt; next retry allowed after 2026-07-11 06:01:16 UTC优先使用 staging(推荐给新部署)
Section titled “优先使用 staging(推荐给新部署)”Let’s Encrypt 的 staging 环境签发不被信任的证书,但基本没有速率限制。用它来确认你的 DNS + 防火墙 + rvpn-server 配置无误,然后再切换到生产:
- 先设
staging = true:[server.acme]enabled = truestaging = truedomains = ["hk.example.com"]cache_dir = "/var/lib/rvpn/acme-staging" - 重启 rvpn-server,在 journal 中留意
ACME event: DeployedNewCert,随后是ACME event: CertCacheStore。如果这对事件触发,说明一切正常。 - 从外部验证 — 浏览器会抱怨不受信任的 staging 证书,这是预期行为:
Issuer 会是
Terminal window echo | openssl s_client -connect hk.example.com:443 -servername hk.example.com 2>&1 | grep -Ei "issuer|verify return code"(STAGING) Let's Encrypt。 - 切换到生产 — 改为
staging = false,并使用全新的cache_dir(staging 证书在另一个账户和缓存中):staging = falsecache_dir = "/var/lib/rvpn/acme" - 重启,等待,验证。Issuer 现在是真实的
Let's Encrypt。
首次在生产模式启动且 cache_dir 为空时,rvpn-server 会打印一条 WARN,正好指向这个流程 — 如果看到这条警告而没有先用 staging 测试过,请先测,以免烧掉速率限制配额。
不要轻易清空 cache_dir。 每次删除 + 重新签发都会计入每周 5 次的 duplicate certificates 限制。跨重启保留该目录,并与其他服务器状态一起备份。
[server]bind_address = "0.0.0.0:443"tls_cert_file = "/etc/rvpn/certs/cert.pem"tls_key_file = "/etc/rvpn/certs/key.pem"identity_key_file = "/etc/rvpn/server_identity.key"websocket_path = "/api/v1/ws"http_port = 80redirect_http_to_https = true
[server.rate_limit]max_connections_per_ip = 500max_handshakes_per_minute = 2000
[server.network]nat_enabled = truedhcp_range = "10.200.0.0/24"
[server.tun]enabled = truetun_ip = "10.200.0.1/24"mtu = 1420interface_name = "tun0"dns_servers = ["1.1.1.1", "8.8.8.8"]轮换身份密钥
Section titled “轮换身份密钥”服务器的身份密钥是客户端首次连接时会固定的密钥。轮换时请生成新密钥对, 并发布一个由旧身份签名的新 prekey bundle,让已固定的客户端能够接受 本次轮换而无需人工干预。
# 让旧身份保留足够长的时间来签署轮换mv server_identity.key old_identity.key
# 生成新身份rvpn-server keygen --output server_identity.key
# 发布由旧身份签名的 v2 bundlervpn-server prekey-bundle \ --identity server_identity.key \ --output prekey-bundle.json \ --rotate-from old_identity.key \ --from-version 1--rotate-from 与 --from-version 必须同时提供 —— 服务器不会发布未签名
的轮换。有关客户端如何处理轮换后的 bundle,参见
服务器身份固定。
反向代理模式
Section titled “反向代理模式”当运行在 Caddy、nginx 或其他终止 TLS 的反向代理之后时,将 bind_address 设为本机端口并让代理处理 TLS。将代理的 WebSocket 转发指向同一路径:
[server]bind_address = "127.0.0.1:8443"tls_cert_file = "" # not used — proxy handles TLStls_key_file = ""websocket_path = "/api/v1/ws"Caddy 示例:
your.domain.com { handle /api/* { reverse_proxy 127.0.0.1:8443 } handle { root * /var/www/html file_server }}路径匹配符必须为
/api/*(前缀匹配),而非/api(仅精确匹配)。