When building a watch party tool, you are faced with a fundamental architectural question: Where does the data go?
Most commercial synchronization tools route your playback data, room credentials, and sometimes even your video traffic through third-party servers. When I built KoalaSync, I wanted a different path. The official public relay server at syncserver.koalastuff.net operates strictly in volatile RAM, retains no logs, and uses zero persistent storage.
But for true privacy advocates, data sovereignty is binary: either you own the infrastructure, or you don't.
In this comprehensive guide, we will unpack the internal architecture of the KoalaSync relay server, walk through the protocol mechanics that keep streams perfectly in sync, and show you how to deploy your own private instance behind major reverse proxies like Caddy, Nginx, or Traefik.
Part 1: Under the Hood of the Stateless Relay
Before deploying the server, let's look at how the data flows. Understanding the separation of concerns between media streaming and synchronization is key to keeping the server lightweight.
1. The Separation of Media and Control
The KoalaSync relay does not proxy, intercept, or touch the video stream. Your Emby, Jellyfin, Plex server, or streaming provider handles the heavy lifting of streaming the video file directly to your friends' browsers.
The relay server exists solely to forward tiny JSON synchronization payloads (e.g., User A clicked play, User B paused, User A seeked to 12:04). Because these messages are only a few hundred bytes, a single-core VPS with 512MB of RAM can easily coordinate watch parties for hundreds of concurrent users.
2. Volatile RAM-Only State
The relay server is built on Node.js and Socket.IO. It does not use databases, Redis caches, or write to the host disk.
- Rooms are represented as transient JavaScript
Mapobjects. - Passwords are never stored in plaintext. They are hashed using a keyed SHA-256 HMAC when the room is created, and only the hash is kept in memory.
- Pruning & Garbage Collection: Empty rooms are deleted instantly. Inactive rooms are cleaned up after 2 hours. If a client's connection drops and remains inactive for 5 minutes, the server's "Reaper" task forcefully evicts their socket state to prevent memory leaks.
If you restart the container, the entire memory state is wiped. There is zero historical trail of who watched what, when, or with whom.
Part 2: Protocol Mechanics & Security Guards
To ensure a self-hosted instance runs securely and reliably, the relay implements several protection systems directly in the codebase:
1. Input Sanitization and Megaphone Routing
The server operates on a "megaphone" routing architecture. When a client emits a media control event, the server does not inspect or manipulate the timeline. Instead, it acts as a sanitizing megaphone, broadcasting the event to all other peers in the room.
However, the server never forwards raw client payloads. In the server backend, every incoming field is sanitized and clamped before re-broadcasting:
const relayPayload = {
senderId: mapping.peerId,
seq: clampNum(data.seq, 0, Number.MAX_SAFE_INTEGER),
currentTime: clampNum(data.currentTime, 0, 86400),
targetTime: clampNum(data.targetTime, 0, 86400),
playbackState: validState(data.playbackState),
username: clamp(data.username, 30),
tabTitle: clamp(data.tabTitle, 100),
mediaTitle: clamp(data.mediaTitle, 100),
volume: clampNum(data.volume, 0, 1),
muted: validBool(data.muted),
peerId: mapping.peerId,
status: typeof data.status === 'string' ? data.status.substring(0, 16) : undefined,
expectedTitle: clamp(data.expectedTitle, 100),
title: clamp(data.title, 100),
actionTimestamp: clampNum(data.actionTimestamp, 0, Number.MAX_SAFE_INTEGER),
};
This sanitization guarantees that malicious clients cannot exploit other connected browsers by injecting oversized strings, execution payloads, or invalid playback structures into the room.
2. Multi-Tier Rate Limiting
To defend against simple Denial of Service (DoS) and brute-force password attempts, the server tracks IP addresses and sockets using high-performance in-memory counters:
- Connections: Maximum of 10 connections per minute per IP.
- Events: Maximum of 30 Socket.IO events per 10 seconds per socket.
- Health Endpoint: Max 10 requests per minute per IP for
/and/health. - Password Brute-Force: If a client fails room authentication 5 times in 2 minutes, their IP is locked out of that room for 15 minutes.
3. The Dual-Heartbeat Architecture
To ensure rooms don't fill up with "ghost" connections when a user closes their laptop or loses network coverage, KoalaSync uses two independent heartbeat loops:
- Background Heartbeat (30s): Sent by the browser extension's service worker to signal that the connection is active.
- Content Heartbeat (15s): Sent by the content script running inside the target video tab. It transmits media playback progress (
currentTime,playbackState,volume).
If both heartbeats cease for more than 5 minutes, the server's background cleanup loop prunes the socket, freeing up resources immediately.
Part 3: Deploying the Server
To self-host the server, you will run the official containerized build.
1. Creating the Docker Compose File
Create a dedicated folder on your server (e.g. /opt/koalasync) and create a docker-compose.yml file:
services:
koalasync:
image: ghcr.io/shik3i/koalasync:latest
container_name: koalasyncserver
restart: unless-stopped
ports:
- "127.0.0.1:3000:3000" # Binds to localhost for security behind a reverse proxy
environment:
- PORT=3000
- MAX_ROOMS=100
- MAX_PEERS_PER_ROOM=20
- MIN_VERSION=1.0.0
# Controls verbose connection logs in console (1 = enabled, 0 = disabled):
- DEBUG_LOGGING=0
# Optional: Admin token (32+ chars) for aggregate stats on /health.
# If omitted or left empty, the admin metrics interface is deactivated
# and the endpoint only outputs basic public service status.
- ADMIN_METRICS_TOKEN=generate_a_long_32plus_character_metrics_token_here2. Configuring Your Reverse Proxy
Because modern browsers block unencrypted WebSockets (ws://) on secure HTTPS pages, you must expose your server using TLS/HTTPS (wss://). Additionally, because the server uses IP-based rate limiting, your reverse proxy must forward the client's real IP address via headers (like X-Forwarded-For).
Below are production-ready configurations for the three most common reverse proxies.
Option A: Caddy (Recommended)
Caddy is the simplest option as it automatically requests and renews TLS certificates and forwards headers correctly out of the box.
syncserver.yourdomain.com {
reverse_proxy localhost:3000
}
Option B: Nginx
If you are already running an Nginx instance, you need to configure it to allow WebSocket upgrade headers and forward the client IP correctly.
# Add this mapping block inside your main nginx.conf (or HTTP block)
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 443 ssl http2;
server_name syncserver.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/syncserver.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/syncserver.yourdomain.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
# Enable WebSocket upgrades
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
# Forward real client IP headers (CRITICAL for rate limiting!)
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Prevent connection drops during quiet phases of the video
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
}
}
Option C: Traefik
If you deploy your services in a Docker-native environment with Traefik, add the following labels directly to your koala-sync service in docker-compose.yml:
services:
koala-sync:
image: ghcr.io/shik3i/koalasync:latest
container_name: koala-sync-server
restart: unless-stopped
expose:
- "3000"
environment:
- PORT=3000
- SERVER_SALT=my_super_secret_salt_string_123!
labels:
- "traefik.enable=true"
- "traefik.http.routers.koalasync.rule=Host(`syncserver.yourdomain.com`)"
- "traefik.http.routers.koalasync.entrypoints=websecure"
- "traefik.http.routers.koalasync.tls.certresolver=myresolver"
- "traefik.http.services.koalasync.loadbalancer.server.port=3000"
# Forward real IP headers
- "traefik.http.middlewares.koalasync-headers.headers.customrequestheaders.X-Forwarded-Proto=https"
- "traefik.http.routers.koalasync.middlewares=koalasync-headers"
Part 4: Verification and Testing
Start your deployment:
docker compose up -d
To verify that your server is running, publicly accessible, and correctly proxying client IPs, open a browser and visit your public endpoint:https://syncserver.yourdomain.com
It should instantly return:
{"status":"online","service":"KoalaSync Relay"}
If you configured the ADMIN_METRICS_TOKEN, you can inspect detailed aggregate performance statistics (memory usage, connection counts, active rooms) using curl:
curl -H "Authorization: Bearer your_metrics_token" https://syncserver.yourdomain.com/health
Part 5: Connecting Clients & The Magic Invitation Link
Now that the server is live, how do you and your friends connect?
1. Setting a Custom Server
In the browser extension popup, click on the Room tab. In the server dropdown, change the setting from Official Server to Custom Server and enter your WSS URL: wss://syncserver.yourdomain.com.
Server: [ Custom Server ]
URL: [ wss://syncserver.yourdomain.com ]
2. Zero-Configuration Invite Links
You might worry that your friends have to go through this configuration process manually every time you want to watch a movie.
They don't.
When you click the copy button to share a room link, the extension generates an invitation URL in this format:
https://sync.koalastuff.net/join.html#join:<roomId>:<password>:1:<encodedServerUrl>
The key element here is the hash (#).
- The
1flag indicates a custom server connection. - The
encodedServerUrlparameter contains your encoded server location.
When your friends click the link, the landing page detects these parameters, and the local KoalaSync extension intercepts the join request. It reads your private server URL out of the hash fragment and automatically connects your friends to your server.
And because this metadata sits in the URL hash fragment (after the #), it is processed strictly client-side by the browser. The details are never sent to the hosting server of sync.koalastuff.net. Your private server remains 100% invisible to the public internet.
Hosting your own KoalaSync relay server gives you complete control over your watch party data pipeline. With a stateless architecture, zero logging, and a secure client connection protocol, you get the absolute best of both worlds: convenient synchronization and compromise-free privacy. Happy hosting!