Remote Access

Remote broadcasters and relay clients connect through a WireGuard tunnel so the mixer
sees them as LAN peers. This avoids exposing the UDP ingest port to the public internet
and keeps latency predictable.


WireGuard Setup

Server side (stg-srv001)

Add each remote peer to the WireGuard interface config (/etc/wireguard/wg0.conf):

[Peer]
# Remote broadcaster studio
PublicKey = <studio machine pubkey>
AllowedIPs = 10.0.0.10/32

[Peer]
# Remote relay
PublicKey = <relay machine pubkey>
AllowedIPs = 10.0.0.20/32

Apply without dropping existing connections:

sudo wg syncconf wg0 <(sudo wg-quick strip wg0)

Verify the peer is connected:

sudo wg show wg0

Look for a latest handshake time within the last few seconds and a non-zero transfer
count.

Remote broadcaster machine

Generate a keypair if you haven’t already:

wg genkey | tee privatekey | wg pubkey > publickey

Configure the interface (/etc/wireguard/wg0.conf or equivalent):

[Interface]
PrivateKey = <local private key>
Address = 10.0.0.10/32
DNS = 10.0.0.1      # optional, if the server runs a DNS resolver

[Peer]
PublicKey = <server public key>
Endpoint = <server-public-ip>:51820
AllowedIPs = 10.0.0.1/32       # only route the mixer's VPN IP through the tunnel
PersistentKeepalive = 25        # keep the tunnel alive through NAT
sudo wg-quick up wg0

Verify connectivity:

ping 10.0.0.1

Then start the broadcaster pointing at the tunnel address:

shiloh-broadcaster connect \
    --server 10.0.0.1:5005 \
    --name studio \
    --channels 2

Firewall Configuration

On stg-srv001, open the WireGuard port and the ingest port for the VPN subnet:

# WireGuard handshake
sudo ufw allow 51820/udp

# Broadcaster ingest from VPN peers
sudo ufw allow from 10.0.0.0/24 to any port 5005 proto udp

# Control plane (only if web-relay or other tools run remotely)
sudo ufw allow from 10.0.0.0/24 to any port 19997 proto udp

# Web UI (restrict if needed)
sudo ufw allow 8889/tcp
sudo ufw allow 8890/tcp

NOTE: Do not expose port 5005 to the public internet. Anyone who can send to that port
can send audio into the mixer ingest. The WireGuard tunnel provides authentication.


DNS and Hostname Resolution

For remote peers, use the WireGuard IP directly in all connection strings. DNS lookups
can fail during tunnel flap and cause the broadcaster to wait for timeout before reconnecting.

# Preferred: use the static VPN IP
shiloh-broadcaster connect --server 10.0.0.1:5005 ...

# Acceptable: use a hostname that resolves to the VPN IP only
shiloh-broadcaster connect --server mixer.vpn:5005 ...

# Avoid: public DNS name that resolves to the NAT address
# shiloh-broadcaster connect --server stg-srv001.example.com:5005 ...

On the remote machine, add a static entry to /etc/hosts for reliability:

10.0.0.1   mixer.vpn   stg-srv001.vpn

Latency Expectations over WAN

WireGuard adds minimal overhead (UDP encapsulation, ChaCha20 encryption). The dominant
factor is the underlying WAN RTT.

WAN RTT Added jitter Recommended --buffer-ms for relay
< 20 ms < 5 ms 50 ms (default)
20–50 ms 5–15 ms 80 ms
50–100 ms 15–30 ms 120 ms
> 100 ms variable 150 ms or more

For a remote broadcaster, jitter shows up as ingest ring underruns on the mixer side.
Check the mixer log for ingest drops or watch the diagnostics in mixer_web. If drops
are frequent, the WAN path has too much jitter for the current packet cadence; consider
reducing JACK buffer size or increasing broadcast_frames.

NOTE: The broadcaster reconnects automatically after WireGuard tunnel drops. The
null-sink on the broadcaster machine remains loaded throughout — apps routed to it
continue to run. Audio resumes within one backoff cycle (1 s for a fresh drop) once the
tunnel is restored.


Remote mixer_web Access

mixer_web listens on :8889 (TCP). You can expose it via WireGuard or via a reverse
proxy with TLS if public access is needed.

Minimal nginx proxy with TLS (certbot certificate):

server {
    listen 443 ssl;
    server_name mixer.example.com;

    ssl_certificate     /etc/letsencrypt/live/mixer.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/mixer.example.com/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:8889;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
    }
}

WebSocket (used by Phoenix LiveView) requires the Upgrade headers to be proxied — the
config above handles this.