RTMP is how video reaches a streaming platform. FFmpeg can send to one directly (from a file, from a camera, or from another stream) which is what makes unattended and automated streaming possible without any desktop software.
The basic command
ffmpeg -re -i input.mp4 -c:v libx264 -preset veryfast -b:v 3000k \ -maxrate 3000k -bufsize 6000k -pix_fmt yuv420p -g 60 \ -c:a aac -b:a 128k -ar 44100 \ -f flv rtmp://live.example.com/app/STREAM_KEY
Every part of that is doing something necessary, and the next sections explain the ones that break streams when omitted.
-re is not optional when streaming a file
Without it, FFmpeg reads the file as fast as it can and pushes an hour of video at the server in a few minutes.
The server accepts it, the stream ends almost immediately, and viewers see something unwatchable. -re makes FFmpeg read at the file's real playback rate, which is what a live stream expects.
Omit it only when the source is genuinely live; a camera or another stream already arriving in real time.
Keyframe interval decides the delay
-g 60 sets a keyframe every 60 frames. At 30 frames per second that is every two seconds.
This matters because viewers can only start playing at a keyframe, and adaptive players switch quality at keyframes. Most platforms specify two seconds, and a stream with a long interval produces slow starts and stuttering quality changes.
Set it to twice your frame rate. Add -keyint_min 60 and -sc_threshold 0 to stop FFmpeg inserting extra keyframes at scene changes, which makes the interval unpredictable.
Cap the bitrate, do not just set it
-b:v is a target, not a limit. A complex scene can exceed it, and a stream that exceeds the upload capacity buffers for every viewer.
-maxrate with -bufsize enforces a ceiling. Set maxrate to what your connection can sustain, and bufsize to about twice that.
Measure your actual upload speed and use around 70% of it. Streaming at the edge of your capacity works until anything else on the connection sends a packet.
pix_fmt yuv420p
Include it. Without it FFmpeg may choose a pixel format that is technically better and that many players and platforms cannot decode.
The symptom is a stream that the platform accepts and nobody can watch, which is a slow thing to diagnose from the outside.
Audio settings platforms expect
AAC at 128k and 44100 Hz is what most platforms want, and a sample rate they do not expect is a common cause of rejection.
Include the audio arguments even when the source has no audio. Many platforms drop a stream with no audio track, if the source is silent, generate silence:
-f lavfi -i anullsrc=channel_layout=stereo:sample_rate=44100
Add that as a second input and map it. A stream that ends after thirty seconds with no error is often this.
Keep the stream key out of the command
The stream key is a credential. Anyone holding it can broadcast as you.
A key typed into a shell command lands in your shell history, and one in a script is in your repository. Put it in an environment variable read at run time, and keep the file holding it out of version control.
If a key is exposed, reset it at the platform. There is no way to un-share it otherwise.
Restreaming to several platforms
One encode, several destinations, using the tee muxer:
-f tee "[f=flv]rtmp://a.example.com/app/KEY1|[f=flv]rtmp://b.example.com/app/KEY2"
This encodes once and sends the result to both, which is far cheaper than running two FFmpeg processes.
The catch: one destination failing can affect the whole output. For anything that matters, a dedicated restreaming service handles failures per destination in a way this does not.
It will drop, so plan for that
A network interruption ends the stream. FFmpeg does not reconnect on its own.
Wrap it in a loop that restarts it, with a short pause so a persistent failure does not become a tight loop hammering the server:
while true; do ffmpeg ... || true sleep 5 done
For anything scheduled or unattended, run it under a process manager instead so it survives a reboot, and never start a long-running stream from an SSH session that will close with your laptop lid.
Streaming a playlist continuously
A common use: a channel playing a rotating set of files.
Use the concat demuxer as the input, with -re, and the list file can be regenerated between runs to change the schedule.
All the files must share codec, resolution and frame rate or the stream breaks at the join. Normalise them to identical settings in advance. Trimming and concatenating walks through making them match.
What to check when it fails
Rejected immediately: wrong key, wrong URL, or the platform is not expecting a stream. Check the key first, and that you did not include the application path twice.
Connects then drops, usually missing audio, or a bitrate above what the platform accepts for your account.
Constant buffering for viewers. The bitrate exceeds your real upload capacity. Lower it and measure.
Video and audio out of sync. A variable frame rate source. Normalise it with -vsync cfrFor reading what FFmpeg reported, see troubleshooting FFmpeg errors.
RTMP is for sending, not for viewing
Browsers no longer play RTMP. It is the ingest protocol, how video gets to a platform, and the platform converts it to HLS or DASH for viewers.
If you are building your own delivery rather than sending to a platform, that conversion is the other half of the job. See setting up HLS streaming.
Watch the encoder keep up with real time
A stream fails when the encoder cannot produce frames as fast as they are consumed, and the warning is in the output.
ffmpeg -re -i in.mp4 -c:v libx264 -preset veryfast -b:v 3000k -f flv rtmp://... 2>&1 | grep -E 'speed=|frame='
The speed figure must stay at or above one. Anything below means the encoder is falling behind, and the stream will stutter or drop regardless of the connection.
The fix is a faster preset or a smaller resolution rather than a higher bitrate. A machine that cannot encode at the chosen settings will not manage it by being asked for more data.
Check the connection can carry it
The upload capacity has to exceed the stream bitrate with room to spare, and most connections are asymmetric.
curl -s -T /dev/zero -o /dev/null -w 'yukleme %{speed_upload} B/s\n' https://example.com/upload 2>/dev/null
ss -tin | grep -A1 rtmp | head
Allow at least half again above the stream bitrate. A connection matched exactly to it fails whenever anything else uses the network, and that includes the machine's own updates.
Where the capacity is genuinely limited, reducing the bitrate produces a watchable stream and increasing it produces an unwatchable one, which is the opposite of what people try first.