If your FFmpeg command already sends a picture to YouTube when you start it by hand, you can put that command in a systemd service and have the Pi launch it during boot. You will still need to check YouTube Studio after a reboot: a service marked active only tells you that the local process is running, not that YouTube is receiving a healthy picture.
This guide assumes the source, encoding settings and YouTube stream key are already working in a manual test. Keep that known-good command, move it carefully into a service, then test each layer—systemd, FFmpeg output and the YouTube preview—rather than treating automatic startup as proof of a live broadcast.
Start with a known-good manual command
Do not make systemd your first debugging environment. Run the complete FFmpeg command in a terminal using the same input, codecs, output settings and stream key that you intend to use for the service. Confirm that YouTube Studio shows the incoming stream and that the preview behaves as expected. If the manual version fails, fix that first; a service cannot make an invalid command valid.
When you have a working command, save a private copy of it as a reference. Note which account runs it, which directory it expects to be in, and whether it reads local files, a camera or another source. A command that works from your home directory may fail when started by systemd because the service has a different working directory and environment. Use absolute paths for FFmpeg, input files and any wrapper script.
Find the FFmpeg executable path with command -v ffmpeg, then use the returned path in the unit. If the command depends on relative paths, either convert them to absolute paths or set an explicit working directory in the unit. Test any wrapper script under the intended service account, not just as your interactive login. A Raspberry Pi forum report of a service failing with 203/EXEC illustrates why execution paths and permissions are worth checking before blaming the stream itself: systemd startup execution errors.
The link above must point to a menu-approved article; use this valid one instead: the systemd service guide covers the broader pattern. Also consider whether the video itself is in a format suitable for a continuous playlist; this guide to video formats and codecs helps separate encoding problems from startup problems.
Keep a copy of the exact command that you tested, but do not put a real stream key into a public script, a screenshot or a message asking for help. If you later change the input, codec or output URL, change one thing at a time and repeat the manual test before editing the service. For a loop assembled from animated clips, the FFmpeg playlist encoding guide can help with the media side of the setup.
Keep the stream key out of the unit
A stream key is a credential: anyone who obtains it may be able to send a broadcast to the associated stream. Avoid embedding it directly in a shell command that you type repeatedly, a script you might publish, or the ExecStart line in a unit file. Commands can end up in shell history, and unit files are often read during troubleshooting or copied for backup.
Instead, put the key in a separate environment file with restricted permissions. The examples below use a placeholder variable name, not a real key. A unit can refer to a value as $YOUTUBE_STREAM_KEY in its ExecStart arguments, while an environment file supplies that value to the service. Do not paste the actual key into this article's example or into a command you share publicly.
For a dedicated service account, make the environment file owned by root and readable only by root and the service's group, or use another permissions arrangement appropriate to your account model. For example, a root-owned file with mode 0640 can be readable by root and a chosen group, but only if the service account belongs to that group. Check both the owner and permissions; a restrictive mode that also prevents the service from reading the file will make startup fail.
A typical file location might be /etc/ffmpeg-youtube.env. Its contents should use the environment-variable syntax expected by systemd, such as YOUTUBE_STREAM_KEY=replace-with-your-key, but do not leave the placeholder in a production setup. Use your real key only in the private file, then confirm that the service account can read the file without making it broadly readable. The systemd documentation for environment files and unit configuration is the right place to check details for your installed system: systemd.exec documentation.
The key also appears indirectly in the destination URL assembled for FFmpeg. Be mindful of tools that print complete process arguments or command lines in logs. Avoid enabling verbose logging that exposes secrets, and redact keys before sharing journal output. If you suspect the key has been disclosed, replace or reset it through the relevant YouTube Live controls and update the protected file; do not assume deleting a copied script has removed every copy.
Create a service unit for the tested command
Create a unit such as /etc/systemd/system/ffmpeg-youtube.service. Choose a specific, non-root user for the service where practical, and set a working directory if your source files depend on one. The values below are examples to adapt: replace the username, paths and command arguments with the ones that worked in your manual test.
[Unit]
Description=FFmpeg YouTube stream
Wants=network-online.target
After=network-online.target
[Service]
Type=simple
User=streamer
WorkingDirectory=/home/streamer/video
EnvironmentFile=/etc/ffmpeg-youtube.env
ExecStart=/usr/bin/ffmpeg -re -stream_loop -1 -i /home/streamer/video/loop.mp4 -c:v copy -c:a aac -f flv "rtmp://example.invalid/live/$YOUTUBE_STREAM_KEY"
Restart=on-failure
RestartSec=10s
[Install]
WantedBy=multi-user.target
The destination is deliberately a placeholder, and the key is represented by a variable. Use the ingest URL and stream-key arrangement from your own working command; do not copy the example output URL. If your real command has many arguments, preserve them carefully. In a unit, ExecStart is not automatically interpreted by a shell, so shell operators such as pipes, redirects and wildcard expansion do not behave as they would in an interactive terminal. If you need shell logic, place it in a carefully permissioned wrapper script, then point ExecStart at that script.
Type=simple is appropriate for a foreground FFmpeg process: systemd considers the service started once it has launched the process. That state does not mean FFmpeg has connected successfully, and it certainly does not mean that the stream is visible in Studio. Keep FFmpeg in the foreground rather than backgrounding it with &; systemd needs to track the process it is meant to supervise.
Set the account and file access deliberately. The service user must be able to read the input video or camera device, access the environment file, and execute FFmpeg. If the input comes from a camera, check that the device is available to this user after boot, not merely to your login session. If the Pi needs a USB drive mounted before the file exists, service ordering alone may not ensure the mount is ready; use the correct mount dependency for your system or delay startup until the source is genuinely available.
A systemd unit is a configuration, not a recipe to paste unchanged. In particular, stream-looping flags, codecs and input handling depend on your source. If your command uses a camera, a file playlist, or an additional script, preserve those semantics and test them outside the unit first. StreamNeo turns an uploaded video into a YouTube stream without requiring your Pi to keep running, which removes the need to maintain this local boot-and-source chain when keeping the computer powered is the part you want to avoid.
Put network ordering in perspective
The Wants=network-online.target and After=network-online.target lines express that the service should be ordered after the system's network-online target and should pull that target in. They are useful for a stream that needs a network connection at launch, but they are not a test of DNS, a working route, a reachable YouTube ingest endpoint or a successful authentication. Network manager behavior also affects what “online” means on a particular Pi installation.
If your system's network-online target is not configured to wait for a real network connection, the service may still start before the connection is useful. If FFmpeg exits quickly when it cannot connect, a restart policy may give it another attempt, but that is a consequence of the process exiting, not a guarantee that the network will recover. If FFmpeg remains alive while its connection is unusable, systemd may see no reason to restart it.
For a static file, waiting for a mounted disk may matter as much as waiting for networking. For a camera, the device can take time to become ready even after the network is available. Add only the dependencies your setup needs, and verify them with an actual reboot. Avoid adding arbitrary long sleeps as a substitute for understanding which resource is missing; a fixed delay can merely hide a race on one boot and fail on another.
The Raspberry Pi can be a sensible local source for a stream, but continuous operation also means maintaining its storage, power, network and source. This Raspberry Pi 24/7 loop guide discusses those practical constraints. If your audience is far from your local connection, this guide to choosing a VPS location addresses a different deployment question; it does not replace testing your own ingest connection.
Reload, enable and start the service
After saving the unit, ask systemd to reload its unit definitions, then start and enable it:
sudo systemctl daemon-reload
sudo systemctl enable --now ffmpeg-youtube.service
enable arranges for systemd to start the unit at the configured boot target in future. --now also requests a start immediately, so you can inspect the result without rebooting. Starting a unit is not the same as creating or scheduling a YouTube broadcast. Your YouTube channel and Live Control Room still need the appropriate stream setup, and you should confirm what arrives there.
Check the unit state and recent logs:
sudo systemctl status ffmpeg-youtube.service
sudo journalctl -u ffmpeg-youtube.service -n 100 --no-pager
If the service does not start, read the first useful error rather than repeatedly restarting it. A syntax problem, a missing executable, a permission denial, an unreadable environment file or a missing input are all local issues that can be investigated from the status output and journal. After editing the unit, run daemon-reload again; after changing only the environment file, a service restart is needed to load the new value.
Before trusting the unit to run unattended, test it under the intended user and check Studio while it is active. If the stream is only meant to run at certain times, be explicit about that schedule and how the unit should stop; a boot-enabled service will otherwise attempt to run whenever its configured target is reached. For other playlist behaviours, such as putting new videos first in a rotation, see the playlist prioritisation guide.
Choose a restart policy without overpromising
The example uses Restart=on-failure, which asks systemd to restart the service when its process exits unsuccessfully. Another common choice is Restart=always, which restarts after an exit even when the process returns successfully, subject to systemd's restart rules. A delay such as RestartSec=10s prevents an immediate, tight restart loop. Choose a policy that fits how your FFmpeg command ends and how you want a deliberate stop to behave.
Neither policy guarantees reconnection or a healthy YouTube picture. Restart policies act on process exit. They do not repair a wrong key, a broken input, a bad command, an unavailable route, or a failure in YouTube's broadcast path. A network fault can leave a process running but unable to deliver a useful stream; in that case, a process-based restart rule may not trigger at all. Conversely, a permanently invalid key can cause repeated failures and repeated attempts without ever producing a picture.
For a loop intended to remain up, Restart=always may suit a command that is expected to run continuously and that can exit after a transient fault. It is still a policy choice, not a recovery system. If the process exits normally because your command is designed to finish, always will start it again; if you stop it through systemd, systemd treats the requested stop differently from an unexpected exit. Read the systemd service documentation before choosing a policy for a command with unusual exit behaviour: systemd.service documentation.
FFmpeg also has protocol-specific reconnection options, but flags documented for HTTP do not form a universal recovery switch for a YouTube RTMP stream. The FFmpeg protocols documentation describes protocol-specific behaviour; use only options relevant to the protocol and input you actually use, and test their effects. Do not add flags by guesswork on the assumption that they will force every failed YouTube connection to reconnect.
A more elaborate setup can include health checks, a watchdog or external monitoring, but those need to test the condition you care about, not merely whether a PID exists. Even a local check cannot prove that viewers see the intended picture. A useful first version is simpler: observe systemd, inspect FFmpeg's own output and verify the Studio preview, then decide whether you need a separate way to detect a stalled stream.
Read logs and check the stream after reboot
Use the journal to follow new messages while testing:
sudo journalctl -u ffmpeg-youtube.service -f
Look for FFmpeg's input opening successfully, output setup, connection messages, warnings and errors. Messages about a missing file, invalid device, denied access or inability to resolve a host point to different parts of the chain. Use systemctl status for the service's current state and recent journal lines, but do not treat a green “active (running)” label as a YouTube health check.
Then perform a real reboot test. Before rebooting, make sure the broadcast is configured as you intend in YouTube Studio and that you can access the Live Control Room. Reboot the Pi, allow it to complete startup, and check the service status and journal again. Open the relevant live stream in Studio and confirm that an incoming signal and preview are present, the picture is moving as expected, and sound is present if your stream includes audio. Do not assume the stream is healthy merely because the service is active or the journal has no new fatal line.
If systemd says the service failed or is inactive, investigate local startup: unit syntax, the absolute FFmpeg path, execution permissions, working directory, service-account permissions, the key file and source readiness. If systemd says it is running but Studio has no preview, inspect FFmpeg output and the Studio state separately. A process can be alive while its input is frozen, its output is rejected, or its connection is no longer useful. The dropped-frame and disconnect monitoring guide covers ongoing observation beyond this first check.
If the preview appears but the intended broadcast is not actually live to viewers, check the Studio controls and broadcast state rather than changing systemd blindly. Systemd starts a local command; it does not press YouTube's controls, create a scheduled event or decide whether a broadcast is public. After any repair, repeat the reboot test. A successful manual restart is useful evidence, but only the post-reboot test checks the behaviour this guide is meant to configure.
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
Does Restart=always guarantee that YouTube reconnects?
No. It tells systemd how to respond when the service process exits; it does not guarantee that FFmpeg reconnects or that YouTube receives a healthy picture. Check FFmpeg output and the Studio preview after a failure.
Why does systemd show the service as running when Studio has no picture?
The active state describes the local process, not the state of the complete path to YouTube. FFmpeg may be stalled, unable to read its source or unable to deliver usable output. Inspect the journal and Studio independently.
Should the stream key go in ExecStart?
Avoid placing the real key directly in the unit or a command you may share. Keep it in a protected environment file with permissions that let the service account read it, and redact it from logs or troubleshooting material.
Does enabling the service create a YouTube Live broadcast?
No. Enabling configures systemd to start the local process at boot; it does not create a broadcast or configure Studio. Set up the YouTube live stream separately and check its incoming signal and preview after reboot.