Server Configuration
The server reads its configuration from a TOML file (default: server.toml).
rvpn-server -c /etc/rvpn/server.tomlMinimal Configuration
Section titled “Minimal Configuration”[server]bind_address = "0.0.0.0:443"tls_cert_file = "/etc/letsencrypt/live/your-domain.com/fullchain.pem"tls_key_file = "/etc/letsencrypt/live/your-domain.com/privkey.pem"identity_key_file = "/etc/rvpn/server_identity.key"Full Example
Section titled “Full Example”[server]bind_address = "0.0.0.0:443"tls_cert_file = "/etc/letsencrypt/live/example.com/fullchain.pem"tls_key_file = "/etc/letsencrypt/live/example.com/privkey.pem"identity_key_file = "/etc/rvpn/server_identity.key"websocket_path = "/api/v1/ws"http_port = 80
[server.network]nat_enabled = truedhcp_range = "10.200.0.0/24"dns_servers = ["1.1.1.1", "8.8.8.8"]
[server.rate_limit]max_connections_per_ip = 500max_handshakes_per_minute = 2000
[server.tun]enabled = truetun_ip = "10.200.0.1/24"mtu = 1420interface_name = "tun0"Key Management
Section titled “Key Management”Generating Keys
Section titled “Generating Keys”Run these commands once when setting up a new server:
cd /etc/rvpn
# Generate the server's long-term identity keyrvpn-server keygen
# Generate the prekey bundle (used by clients to authenticate)rvpn-server prekey-bundleThis produces:
server_identity.key— the server’s Ed25519 identity key pair. Back this up and keep it private.prekey-bundle.json— the public prekey bundle. Distribute this to clients.prekey-bundle.private.json— private signed prekey material. Keep this private.
Rotating Prekeys
Section titled “Rotating Prekeys”Prekey rotation is not automated in the current server — regenerate the prekey bundle manually with rvpn-server prekey-bundle when you want to rotate. Clients do not need updated prekey bundles when the server’s one-time prekeys are consumed; they only need a fresh bundle if the server’s identity key changes.
Rotating the identity key
Section titled “Rotating the identity key”Existing clients pin the server’s identity key on first connect (see Server Identity Pinning). To rotate the identity key without breaking those clients, sign the new bundle with the old identity so the client can verify the chain:
cd /etc/rvpn
# Preserve the old identity long enough to sign the rotationmv server_identity.key old_identity.key
# Generate the new onervpn-server keygen --output server_identity.key
# Publish a v2 bundle signed by old_identity.keyrvpn-server prekey-bundle \ --identity server_identity.key \ --output prekey-bundle.json \ --rotate-from old_identity.key \ --from-version 1Both --rotate-from and --from-version are required together. Ship the
new prekey-bundle.json to clients as usual — already-pinned clients
update silently, new clients pin the new key on first use. Archive
old_identity.key after publishing so you can chain a future rotation
from it if you ever need to.
TLS Certificate Renewal
Section titled “TLS Certificate Renewal”Let’s Encrypt certificates expire every 90 days. Certbot installs a renewal cron job automatically.
After renewal, restart rVPN to pick up the new certificate:
sudo systemctl restart rvpn-serverTo automate this, add a deploy hook:
#!/bin/bashsystemctl restart rvpn-serverchmod +x /etc/letsencrypt/renewal-hooks/deploy/rvpn-reload.shDecoy Response
Section titled “Decoy Response”The server automatically returns a hardcoded nginx-style 404 page for any non-WebSocket request. From the outside this looks like an unconfigured web server rather than a VPN endpoint. For a fully custom decoy site, terminate TLS at a reverse proxy and forward only /api/* to rVPN — see Reverse Proxy Setup for Caddy, nginx, and HAProxy recipes with a real decoy website served at the root.
Running Without TLS (Reverse Proxy Mode)
Section titled “Running Without TLS (Reverse Proxy Mode)”If you’re running behind Caddy, nginx, or HAProxy, omit the cert/key files and bind to a local port:
[server]bind_address = "127.0.0.1:8443"websocket_path = "/api/v1/ws"# No tls_cert_file / tls_key_file — TLS terminated at the proxySee Reverse Proxy Setup for complete Caddy, nginx, and HAProxy configurations, including decoy site setup and multi-server load balancing.
Rate Limiting
Section titled “Rate Limiting”The server rate-limits incoming connections per client IP address to prevent abuse.
[server.rate_limit]max_connections_per_ip = 500max_handshakes_per_minute = 2000max_connections_per_ip— Maximum concurrent connections from a single IP address.max_handshakes_per_minute— Maximum new handshake attempts per IP per minute.
SOCKS5 Clients Need Higher Limits
Section titled “SOCKS5 Clients Need Higher Limits”[!WARNING] In legacy (non-multiplexed) SOCKS5 mode, the protocol opens one WebSocket connection per TCP flow. A modern browser page can easily open 20–50 concurrent connections (HTML, CSS, JS, images, API calls). If your rate limits are too low, connections will be silently dropped and pages will fail to load.
For servers serving legacy SOCKS5 clients, set
max_connections_per_ipto at least 500 andmax_handshakes_per_minuteto at least 2000. Adjust upward based on your expected concurrent connections per user.
[!NOTE] Multiplexed SOCKS5 mode (single WebSocket, multiple flows) is the default in modern clients. It eliminates rate limit concerns entirely since all flows share one connection. Set
multiplex = truein your client config to use it.
When a connection is rate-limited, the server logs Rate limited: <IP> at debug level and closes the connection without an error response.
All Options
Section titled “All Options”See the Server Configuration Reference for a complete list of every available setting.