Server Configuration¶
The server reads its configuration from a TOML file (default: server.toml).
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¶
[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 = true
dhcp_range = "10.200.0.0/24"
dns_servers = ["1.1.1.1", "8.8.8.8"]
[server.rate_limit]
max_connections_per_ip = 500
max_handshakes_per_minute = 2000
[server.tun]
enabled = true
tun_ip = "10.200.0.1/24"
mtu = 1420
interface_name = "tun0"
Key Management¶
Generating Keys¶
Run these commands once when setting up a new server:
cd /etc/rvpn
# Generate the server's long-term identity key
rvpn-server keygen
# Generate the prekey bundle (used by clients to authenticate)
rvpn-server prekey-bundle
This 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¶
Prekeys rotate automatically every 7 days (configurable via prekey_rotation_hours). Clients do not need updated prekey bundles when prekeys rotate — only if the server's identity key changes.
TLS Certificate Renewal¶
Let's Encrypt certificates expire every 90 days. Certbot installs a renewal cron job automatically.
After renewal, reload r-vpn to pick up the new certificate:
sudo systemctl reload rvpn-server
# or if reload isn't supported:
sudo systemctl restart rvpn-server
To automate this, add a deploy hook:
# /etc/letsencrypt/renewal-hooks/deploy/rvpn-reload.sh
#!/bin/bash
systemctl reload rvpn-server 2>/dev/null || systemctl restart rvpn-server
Decoy Website (Optional)¶
To make the server look like a normal HTTPS website to casual probes, point decoy_root at a directory of static HTML files:
Any request that is not a valid r-vpn WebSocket upgrade will receive the decoy site.
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 proxy
See Reverse Proxy Setup for complete Caddy, nginx, and HAProxy configurations, including decoy site setup and multi-server load balancing.
Rate Limiting¶
The server rate-limits incoming connections per client IP address to prevent abuse.
max_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¶
[!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¶
See the Server Configuration Reference for a complete list of every available setting.