This guide walks you through building, running, and testing the go-rtmp server from scratch.
- Go 1.21+ installed (download)
- FFmpeg installed (provides
ffmpegandffplayfor testing) - Optionally: OBS Studio for live streaming from a camera/screen
cd go-rtmp
go build -o rtmp-server ./cmd/rtmp-serverOn Windows this produces rtmp-server.exe.
./rtmp-server -listen :1935 -log-level infoThe server is now accepting RTMP connections on port 1935.
mkdir -p recordings
./rtmp-server -listen :1935 -log-level info -record-all true -record-dir ./recordingsEvery published stream will be saved as an FLV file in the recordings/ directory.
./rtmp-server -listen :1935 -relay-to rtmp://cdn1.example.com/live/key -relay-to rtmp://cdn2.example.com/live/keyThe server will forward all incoming media to the specified destinations.
# Generate a self-signed certificate (for testing)
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 \
-nodes -keyout key.pem -out cert.pem -days 365 -subj "/CN=localhost"
# Run with both RTMP (plaintext) and RTMPS (encrypted)
./rtmp-server -listen :1935 -tls-listen :443 -tls-cert cert.pem -tls-key key.pemThe server will accept plaintext RTMP on port 1935 and encrypted RTMPS on port 443 simultaneously.
For production, use certificates from Let's Encrypt or another CA:
./rtmp-server -listen :1935 \
-tls-listen :443 \
-tls-cert /etc/letsencrypt/live/stream.example.com/fullchain.pem \
-tls-key /etc/letsencrypt/live/stream.example.com/privkey.pemStreaming clients use rtmps:// URLs:
# Publish over RTMPS
ffmpeg -re -i test.mp4 -c copy -f flv rtmps://localhost:443/live/test
# Watch over RTMPS
ffplay rtmps://localhost:443/live/testOBS Studio: Set Server to rtmps://your-server:443/live and Stream Key as usual.
# Log all events as JSON to stderr (for log pipelines)
./rtmp-server -listen :1935 -hook-stdio-format json
# Call a webhook when a stream starts publishing
./rtmp-server -listen :1935 -hook-webhook "publish_start=https://api.example.com/on-publish"
# Run a script when a client connects
./rtmp-server -listen :1935 -hook-script "connection_accept=/opt/scripts/on-connect.sh"
# Combine multiple hooks
./rtmp-server -listen :1935 \
-hook-stdio-format json \
-hook-webhook "publish_start=https://api.example.com/on-publish" \
-hook-script "connection_accept=/opt/scripts/on-connect.sh"Available event types: connection_accept, connection_close, publish_start, play_start, codec_detected, auth_failed.
# Enable metrics endpoint on port 8080
./rtmp-server -listen :1935 -metrics-addr :8080Query metrics at http://localhost:8080/debug/vars — returns JSON with all RTMP counters (connections, publishers, subscribers, media bytes, relay stats, uptime).
# Static tokens via CLI flags
./rtmp-server -listen :1935 \
-auth-mode token \
-auth-token "live/stream1=secret123" \
-auth-token "live/camera1=cam_token"
# Token file (JSON: {"live/stream1": "secret123"})
./rtmp-server -listen :1935 -auth-mode file -auth-file tokens.json
# Webhook callback (POST JSON to your auth service)
./rtmp-server -listen :1935 -auth-mode callback -auth-callback https://auth.example.com/validateWhen authentication is enabled, clients must include a token in the stream name:
# Publish with token
ffmpeg -re -i test.mp4 -c copy -f flv "rtmp://localhost:1935/live/stream1?token=secret123"
# Play with token
ffplay "rtmp://localhost:1935/live/stream1?token=secret123"OBS Studio: set Server to rtmp://localhost:1935/live and Stream Key to stream1?token=secret123.
| Flag | Default | Description |
|---|---|---|
-listen |
:1935 |
TCP address to listen on |
-log-level |
info |
Log verbosity: debug, info, warn, error |
-record-all |
false |
Record all published streams to FLV files |
-record-dir |
recordings |
Directory for FLV recordings |
-chunk-size |
4096 |
Outbound chunk payload size (1-65536 bytes) |
-relay-to |
(none) | RTMP URL to relay streams to (repeatable) |
-auth-mode |
none |
Authentication mode: none, token, file, callback |
-auth-token |
(none) | Stream token: streamKey=token (repeatable, for token mode) |
-auth-file |
(none) | Path to JSON token file (for file mode) |
-auth-callback |
(none) | Webhook URL for auth validation (for callback mode) |
-auth-callback-timeout |
5s |
Auth callback HTTP timeout |
-hook-script |
(none) | Shell hook: event_type=/path/to/script (repeatable) |
-hook-webhook |
(none) | Webhook: event_type=https://url (repeatable) |
-hook-stdio-format |
(disabled) | Stdio output format: json or env |
-hook-timeout |
30s |
Hook execution timeout |
-hook-concurrency |
10 |
Max concurrent hook executions |
-metrics-addr |
(disabled) | HTTP address for metrics endpoint (e.g. :8080). Empty = disabled |
-version |
Print version and exit |
# Stream a local video file to the server
ffmpeg -re -i test.mp4 -c copy -f flv rtmp://localhost:1935/live/test
# Or generate a test pattern (no video file needed)
ffmpeg -re -f lavfi -i testsrc=size=640x480:rate=30 \
-f lavfi -i sine=frequency=440:sample_rate=44100 \
-c:v libx264 -preset ultrafast -tune zerolatency \
-c:a aac -f flv rtmp://localhost:1935/live/test
# H.265 via Enhanced RTMP (requires FFmpeg 6.1+ with libx265)
ffmpeg -re -f lavfi -i testsrc=size=640x480:rate=30 \
-f lavfi -i sine=frequency=440:sample_rate=44100 \
-c:v libx265 -preset ultrafast \
-c:a aac -f flv rtmp://localhost:1935/live/testffplay rtmp://localhost:1935/live/testOpen several terminals and run ffplay in each — they all receive the same stream independently:
# Terminal 2
ffplay rtmp://localhost:1935/live/test
# Terminal 3
ffplay rtmp://localhost:1935/live/test- Open OBS Studio → Settings → Stream
- Set Service to "Custom"
- Set Server to
rtmp://localhost:1935/live - Set Stream Key to
mystream - Click "Start Streaming"
The server will log the connection and begin recording/relaying.
# List recorded files
ls recordings/
# Play a recording
ffplay recordings/live_mystream_20260302_143000.flv
# Check file details
ffprobe recordings/live_mystream_20260302_143000.flv# All tests
go test ./...
# With verbose output
go test -v ./internal/rtmp/chunk/
# Specific package
go test ./internal/rtmp/server/
# Integration tests only
go test ./tests/integration/| Symptom | Cause | Fix |
|---|---|---|
| "connection refused" | Server not running | Start the server first |
| Black screen in ffplay | Missing sequence headers | Restart the publisher — the server caches headers for late joiners |
| "stream not found" in play | Wrong stream key | Ensure publisher and subscriber use the same app/streamName |
| High CPU usage | Debug logging | Use -log-level info instead of debug |
| Recording file empty | Publisher disconnected before keyframe | Stream for at least a few seconds |
| Connection dropped after ~90s | TCP read deadline | The server closes idle connections after 90 seconds of silence — ensure the publisher is actively streaming |
The server automatically manages zombie connections:
- Read deadline (90s): If no data arrives from a client within 90 seconds, the connection is closed
- Write deadline (30s): If the server cannot write to a client within 30 seconds, the connection is dropped
These deadlines reset on each successful I/O operation, so active streams are unaffected.
- Read the Architecture Guide to understand the system design
- Read the RTMP Protocol Reference for wire-level details
- Read the Implementation Guide for code-level walkthrough