WebSocket-to-RTSP audio/video server for VRChat.
This installation guide has only been tested on Debian 13. All commands below assume that you are using the root user.
apt install -y curl build-essential
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
. "$HOME/.cargo/env"Clone the repository into the directory where you want to keep the server, then build it:
git clone https://github.com/vard88508/VRCStreamer.git
cd VRCStreamer/server
chmod +x build.sh create_service.sh kill_service.sh
./build.shThe compiled executable is written to VRCStreamer/server/VRCStreamer.
Create your local .env file from the provided example:
cp .env.example .env
nano .envBefore starting the server, check your TLS path settings:
TLS_CERT_PATHandTLS_KEY_PATH: paths to the certificate and private key for your API domain. You can make them using Certbot
The complete list of settings is available in the Environment section.
./create_service.shThe script creates /etc/systemd/system/VRCStreamer.service, enables automatic startup, and starts the server.
Useful commands:
systemctl status VRCStreamer
systemctl restart VRCStreamer
journalctl -u VRCStreamer -fTo stop the server and remove its systemd service:
./kill_service.shThis does not delete the executable, .env, placeholders, or source files.
Open stream.vard.cc, choose Custom Server, and enter your server's API address:
https://example.com
cd VRCStreamer
git pull
cd server
./build.shIf the systemd service is installed, build.sh automatically restarts it after a successful build.
To block publishing to specific stream URLs, create blacklist.txt next to the VRCStreamer executable. Add the 32-character stream IDs from the URLs, separated by semicolons or new lines:
a85c0211c512828c4c52dc5716a79e3a;1dd5a1d78b07336b21496ccf7bf79b8a
The server reloads the file every minute. Newly blocked streamers are disconnected, and their IP addresses are blocked from starting new streams. Attempting to publish to an already blocked stream ID also blocks that streamer's IP. Each IP block is linked to the stream IDs that caused it: removing an ID releases its IPs unless another blacklisted ID also blocks them. Removing the file or leaving it empty clears both the stream ID blacklist and its IP blocks. Invalid updates are ignored so the previous valid list remains active.
IP blocks are stored only in RAM and cleared on restart. Other active streams from the same IP and RTSP listeners are unaffected. Logs identify connections with anonymous peer IDs, without exposing IP addresses.
The server stores at most 65,536 stream ID/IP associations. If another association cannot be stored, new streams are rejected until all IDs that overflowed this limit are removed from the blacklist or the server restarts.
The values below match .env.example and the server's built-in defaults.
| Name | Meaning |
|---|---|
SERVER_NAME |
Server name shown by the web client Default value: Self-Hosted Instance |
SERVER_DESCRIPTION |
Optional server description sent to the web client Default value: empty |
POLICIES_LINK |
Optional policies page Default value: empty |
REPORT_ABUSE_LINK |
Optional abuse report page Default value: empty |
ROOT_REDIRECT_URL |
Destination for visitors who open the API root / in a browserDefault value: https://stream.vard.cc |
BIND_ADDR |
Address and port used by the HTTP/HTTPS API and streamer WebSocket Default value: 0.0.0.0:443 |
TLS_CERT_PATH |
Path to the PEM certificate; set both TLS paths to none to disable TLSDefault value: /etc/letsencrypt/live/example.com/fullchain.pem |
TLS_KEY_PATH |
Path to the PEM private key; set both TLS paths to none to disable TLSDefault value: /etc/letsencrypt/live/example.com/privkey.pem |
RTSP_BIND_ADDR |
Address and port used by RTSP listeners Default value: 0.0.0.0:554 |
RTSP_PUBLIC_BASE |
Public RTSPT base URL sent to clients; none derives it from the API hostname and RTSP portDefault value: none |
ALLOWED_ORIGINS |
Comma-separated web client origins allowed to publish streams Default value: https://stream.vard.cc |
ALLOW_ANY_ORIGIN |
Allow publishing from any website; keep this false unless you specifically need itDefault value: false |
PASSWORD |
Optional comma-separated publishing passwords; listeners do not need a password Default value: empty |
VIDEO |
Enable H.264 video publishing; false restricts publishers to audioDefault value: true |
AVAILABLE_VIDEO_QUALITY |
Video presets in widthxheight*fps/bitrate-kbps format; each preset's bitrate is also its sustained ingest limitDefault value: 1280x720*30/2000, |
MAX_H264_FRAME_BYTES |
Maximum size of one H.264 access unit. Values above 8388608 are rejected to bound single-message memory useDefault value: 524288 |
VIDEO_INGEST_BURST_SECS |
Video byte/frame token-bucket capacity measured in seconds at the selected preset. Higher values allow longer short-term delivery spikes without changing the encoder target or allocating a media queue Default value: 2 |
MAX_CONNECTIONS |
Maximum concurrent streamer WebSockets and accepted RTSP connections, including pending RTSP handshakes. Set to 0 to disable this limitDefault value: 320 |
MAX_STREAMERS |
Maximum active streamers. Set to 0 to disable this limitDefault value: 0 |
MAX_STREAMERS_PER_IP |
Maximum active streamers from one IP address. Set to 0 to disable this limitDefault value: 3 |
MAX_LISTENERS_TOTAL |
Maximum active RTSP listeners across all streams. Set to 0 to disable this limitDefault value: 0 |
MAX_LISTENERS_PER_STREAM |
Maximum RTSP listeners on one stream URL. Set to 0 to disable this limitDefault value: 105 |
MAX_LISTENERS_PER_IP |
Maximum accepted RTSP connections from one IP address, including pending handshakes. Set to 0 to disable this limitDefault value: 6 |
EGRESS_KBPS_PER_LISTENER |
Per-listener value used only to estimate outgoing bandwidth in server statistics Default value: 384 |
MAX_HTTP_REQUESTS_PER_IP |
Maximum /healthz, /stats, and /ingest handshake requests from one IP per rate-limit window. Set to 0 to disable this limitDefault value: 60 |
HTTP_RATE_LIMIT_WINDOW_SECS |
HTTP rate-limit window in seconds Default value: 60 |
MAX_RTSP_REQUESTS_PER_CONNECTION |
Maximum RTSP commands on one TCP connection, including playback setup and keepalives. Set to 0 to disable this limitDefault value: 4096 |
RTSP_HANDSHAKE_TIMEOUT_SECS |
Seconds allowed for a new RTSP connection to complete setup and start playback Default value: 30 |
CHANNEL_BUFFER |
Shared frame queue per stream, not per listener; larger values tolerate more listener jitter but retain more media in memory for every active stream Default value: 128 |
STREAMER_IDLE_TIMEOUT_SECS |
Disconnect a streamer after this many seconds without a WebSocket message Default value: 120 |
RUST_LOG |
Server log level, such as error, warn, info, debug, or trace; trace also logs RTSP request headers with sensitive values redactedDefault value: warn |