To add a static overlay to an Nginx RTMP YouTube stream, have FFmpeg read the incoming RTMP video and a transparent image, composite them with its overlay filter, encode the result and publish it to YouTube’s ingest address. NGINX RTMP can receive or relay streams, but it does not composite the image; FFmpeg performs that work.
You can run FFmpeg as a separately supervised process, or use the NGINX RTMP module’s exec_push option to start it when a stream is published. The command below is a starting pattern, not a universal recipe: paths, input timestamps, build options, output profile and ingest details all need checking in your environment.
Prepare a transparent logo image
Use a transparent PNG for a logo or other still graphic. The transparent area lets the programme video show through; opaque pixels remain visible over it. Export the image with only the graphic and the amount of transparency you need, rather than placing it on a solid-colour background and expecting the filter to remove that colour.
Choose dimensions with the final output in mind. A logo that looks clear in a large design file can become difficult to read at 720p, while an oversized mark can cover a singer’s face, subtitles or a news ticker. Make a short local test at the intended output resolution and inspect it at normal viewing size. For a devotional channel, for example, a small mark in a quiet corner may be preferable to a large graphic over the central image.
The image must be readable by the account and process that run FFmpeg. Check its full path, spelling and file permissions. If FFmpeg is launched by a service manager or by NGINX, it may run under a different user from your login shell. A file that opens in your desktop session is not necessarily available to that process.
A static overlay is not a substitute for a source image that matches the stream’s visual style. Keep essential material such as captions and lower thirds clear, and check how the logo looks against both light and dark parts of the picture. If your channel uses an audio-led format, see the practical considerations in this guide to setting audio for a lofi YouTube live stream; the overlay still changes video frames, even when the programme is mainly music.
Read the incoming RTMP feed with FFmpeg
The media path has distinct stages: a publisher sends a stream to an NGINX RTMP application; FFmpeg reads that local RTMP address as its main input; FFmpeg reads the logo as another input; the filter graph composites the two; encoders create output streams; and FFmpeg publishes those streams to YouTube. NGINX is a point in the route, not the image compositor.
A local input might look like rtmp://127.0.0.1:1935/live/STREAM, where live is the application and STREAM is the published stream name in your setup. Replace it with the actual address and confirm that FFmpeg can reach it. If the source is published elsewhere, use that source URL instead, subject to access controls and network reachability.
Before adding the overlay, test that FFmpeg can read the incoming video and audio. Check the input’s codecs, frame size, frame rate, audio presence and timestamps. These properties determine what you map and encode later. An optional audio mapping such as -map '0:a?' avoids failing solely because the input has no audio stream, but it does not guarantee the output audio is suitable for YouTube.
FFmpeg builds differ. Confirm that the binary you will actually run has the required input support, overlay filter and chosen encoders. The FFmpeg filters reference documents the filter’s inputs and positioning expressions. Where input timestamps start at different values, the FFmpeg documentation advises considering setpts=PTS-STARTPTS; test timestamp handling on your live source rather than assuming every input begins together.
Composite the image with the overlay filter
FFmpeg’s overlay filter takes the first video input as the main picture and places the second over it. In a filter graph, [0:v] refers to the video from the first input and [1:v] to the image’s video stream. A simple graph is [0:v][1:v]overlay=10:10[v], which positions the image at coordinates near the top-left of the output; [v] names the composited video for mapping later.
The placement values are coordinates, not a promise that the graphic fits every input. Check the image dimensions and output frame size. The filter supports expressions, so you can position a logo relative to the frame rather than hard-coding a particular resolution. For example, a lower-left placement can be expressed using the main frame height and overlay height. Confirm the result in the preview, especially if the source has black borders, subtitles or a changing crop.
One documented example uses a transparent PNG placed ten pixels from the lower-left corner. The point is the filter’s coordinate mechanism, not that this margin suits your channel. A small margin may disappear under television overscan or sit too close to a platform control; a larger one may consume useful picture area. Test placement against real scenes and the display sizes your viewers are likely to use.
A logo image is a second input, not an instruction to NGINX. If it fails to appear, troubleshoot the image path, alpha channel, filter graph and output mapping. Check FFmpeg’s error output for an unreadable image or unavailable filter. If the source and logo have mismatched timestamps, test the filter graph with timestamp normalisation where appropriate.
Encode the resulting stream
Compositing changes the video frames. That means video cannot simply be passed through with -c:v copy; FFmpeg needs to encode the modified video. The choice of encoder and preset is a balance between output quality, encoding load and available CPU headroom. If the machine cannot encode in real time, frames may lag or the output may become unstable, so test under sustained load before using the setup for a channel that needs to run overnight.
YouTube’s current live encoder guidance lists H.264, H.265 or AV1 video, up to 60 fps, constant bitrate (CBR), and a recommended two-second keyframe interval with a four-second maximum. It recommends RTMPS. These requirements and recommendations can change; check the current YouTube encoder guidance before a production broadcast.
For H.264, the guidance currently lists recommended ingest ranges of 5–14 Mbps for 1080p at 30 fps and 3–8 Mbps for 720p at 30 fps. The table shows examples, not a requirement to use the top of a range. Choose for your resolution, frame rate, upload capacity and encoder behaviour, and leave practical headroom on the connection rather than planning around its best moment.
| H.264 output example | YouTube recommended ingest bitrate |
|---|---|
| 720p at 30 fps | 3–8 Mbps |
| 1080p at 30 fps | 5–14 Mbps |
The sample -g 60 value below assumes 30 frames per second and corresponds to a two-second GOP at that frame rate. At another frame rate, calculate a keyframe interval that matches YouTube’s current recommendation. Also set the output pixel format and audio encoder to compatible choices; the example uses yuv420p and AAC, but your source and selected profile still need testing. Audio can only be copied if its codec and stream parameters are accepted by the destination.
Publish to YouTube’s ingest URL and key
In YouTube Studio, obtain the current server address and stream key for the live setup you intend to use. Treat the key like a password: do not paste it into a public configuration, screenshot, support post or article. A separately managed FFmpeg service can keep the key in a restricted environment or secret store instead of putting it directly in a shared NGINX configuration.
The following command is an illustrative pattern assembled from the two-input overlay mechanism and current YouTube guidance. It has not been tested against your build or input. Replace the local RTMP URL, image path, output settings, destination and key; verify the still-image looping behaviour for your FFmpeg version using its installed manual or ffmpeg -h demuxer=image2. Test with a short recording before a live broadcast.
ffmpeg \
-i 'rtmp://127.0.0.1:1935/live/STREAM' \
-loop 1 -i '/path/to/logo.png' \
-filter_complex '[0:v][1:v]overlay=10:10[v]' \
-map '[v]' -map '0:a?' \
-c:v libx264 -preset veryfast -tune zerolatency \
-pix_fmt yuv420p -g 60 \
-c:a aac -b:a 128k \
-f flv 'rtmp://a.rtmp.youtube.com/live2/STREAM_KEY'
The command illustrates the flow, not a guarantee that its arguments behave identically across builds, sources or output settings. In particular, the image demuxer’s looping option and end-of-file behaviour should be verified locally. Use YouTube’s current RTMPS address when available, rather than copying an old relay example. Confirm the selected protocol and destination in the live setup page.
If you use FFmpeg to produce a processed stream and then publish it into another NGINX RTMP application, that application can use its push directive to forward the stream to a remote server. The directive relays a stream; it does not add the logo. The nginx-rtmp module directives documentation describes push and its reconnect settings. Keep the distinction clear when diagnosing a missing overlay: the composition must already have happened upstream.
Run FFmpeg separately or with exec_push
A separate FFmpeg process is often easier to understand when you are building the pipeline. Publish the original feed to NGINX, run FFmpeg against its local RTMP URL, and supervise FFmpeg as its own service. This makes its logs and restart policy distinct from NGINX’s, and it can keep the YouTube key outside the NGINX configuration. You must still configure process monitoring, permissions, and safe secret handling.
The nginx-rtmp module’s exec_push option offers a different lifecycle. Its documentation says the module runs the configured command for each published stream, supports substitutions such as $app and $name, and terminates the child when publishing stops. It is intended for uses including FFmpeg transcoding and republishing. In practice, ensure the command receives the correct stream address and that a new publisher does not create an unexpected duplicate output.
| Approach | Process lifecycle | Key and permissions | Operational checks |
|---|---|---|---|
| Separate FFmpeg service | One process managed independently for the chosen source | Can keep the YouTube key outside NGINX configuration; image and binary access still matter | Service logs, restart policy, input availability and sustained encoding load |
exec_push |
A child process is started for each published stream and ends when publishing stops | Key may be present in command configuration; restrict access and check the NGINX user’s file permissions | Correct $app and $name substitutions, executable path, child logs and publish/stop behaviour |
NGINX push relay |
Forwards a stream that another stage has already published | Destination credentials and configuration require careful handling | Reconnect behaviour and whether the upstream stream is already composited |
When using exec_push, use a full FFmpeg binary path where practical and quote paths carefully. The NGINX process environment may not include the same PATH as your shell; the module documentation notes that NGINX normally clears the environment. Check that the configured user can read the image, execute the binary and write any logs. Do not assume a command that works interactively will work when launched by NGINX.
Choose the separate-process route if independent supervision and simpler secret handling matter most. Choose exec_push if per-publish process lifecycle fits your existing NGINX configuration and you are prepared to inspect child-process behaviour. Neither approach removes the need to monitor failures or account for re-encoding load. For more on keeping a long-running loop alive after interruptions, see this FFmpeg stream recovery guide.
Check the YouTube preview and placement
Before relying on the stream, use a short controlled test. Confirm that the source reaches FFmpeg, the overlay appears in the intended corner, audio is present if needed, and YouTube’s preview reports an acceptable incoming signal. Watch several scenes, not only a static test frame: logos can obscure a face, a lyric line, a news ticker or a key part of a product demonstration when the underlying picture changes.
Check the output resolution, frame rate, keyframe interval and bitrate against the profile you selected and YouTube’s current guidance. Inspect FFmpeg’s logs for input reconnects, dropped frames, filter errors and encoder warnings. Then check the process logs after stopping and restarting the publisher. A test that works for a few minutes does not establish that a process will recover properly after a network interruption or overnight source failure.
If the overlay disappears while the stream continues, separate the failure points: verify the image file remains available, confirm the FFmpeg process is still using the expected filter graph, and inspect whether a restart used a different working directory or user. If the whole output stops, check the incoming RTMP feed, FFmpeg exit status, YouTube ingest state and network route before changing bitrate blindly.
When your concern is the continued operation of a file-based channel rather than a local RTMP pipeline, the operating model differs. StreamNeo can remove the need to keep a local computer running for an uploaded-video broadcast, which addresses that specific power and restart burden; it is YouTube-only and does not replace FFmpeg composition in the NGINX pipeline described here. For a machine-hosted setup, this checklist on keeping a rain-sounds stream running overnight can help organise restart and monitoring checks.
Before committing, compare the operating options on the pricing page. When the file and channel are ready, start free — 24-hour trial, no card.
FAQ
How do I put a logo on an RTMP live stream?
Read the RTMP feed and a transparent logo as separate FFmpeg inputs, then use the overlay filter and encode the resulting video. NGINX RTMP can receive or relay streams, but it does not perform the composition itself.
Can I use -c:v copy after adding the overlay?
No. The overlay changes video frames, so the video must be encoded rather than copied unchanged. Audio may be copied only if its codec and stream parameters are accepted by the output platform.
Should I use a separate FFmpeg process or exec_push?
A separate process is often simpler to supervise and can make secret handling more straightforward. exec_push ties a child process to each published stream; use it when that lifecycle suits your setup, and check executable paths, permissions, logging and process behaviour.
Will the example command work unchanged on my server?
Not necessarily. FFmpeg builds, input timestamps, image-looping behaviour, available encoders and YouTube settings vary, so verify the local manual and test the complete path before broadcasting.