A terminal session is a poor home for an FFmpeg process that needs to run for weeks. A systemd service starts it after boot, restarts it after an unexpected exit and gives you a consistent place to inspect its logs.
That does not make the stream unbreakable. The service cannot repair a revoked stream key, a missing video file, a broken network path or a YouTube broadcast that has ended for a reason outside the Linux machine. This guide gives you the exact arrangement and the limits you need to test before leaving it overnight.
Why a terminal session is the wrong home for a month-long process
If you start FFmpeg by typing a command into an SSH session, the process is tied to several things that are easy to overlook. The shell may close, the SSH connection may be interrupted, the machine may reboot for maintenance, or the process may exit after an input or network error. nohup and terminal multiplexers can help with disconnection, but they do not provide a boot-time service definition, restart policy or a normal log trail.
A service manager gives the process a defined owner, working directory, environment, command and shutdown behaviour. It can also make the difference between an obvious failure and a silent one: systemctl status shows whether systemd believes the process is running, while journalctl keeps the process output attached to the service.
This is a useful DIY arrangement when you already have a Linux machine, a reliable connection and a reason to control the whole box. If your main requirement is that your own computer can be switched off, a hosted workflow may remove more maintenance. The guide to keeping a YouTube live stream running while your laptop is off explains that distinction before you commit to operating a machine yourself.
A systemd service also gives you a clean failure boundary. It can restart FFmpeg, but it cannot know whether the new process is producing a healthy picture at YouTube. You still need to check YouTube Studio, the stream preview and the output from FFmpeg.
The unit file, line by line
The example below assumes:
- FFmpeg is installed at
/usr/bin/ffmpeg. - A Linux user called
streamowns the working directory. - The loop file is
/srv/stream/loop.mp4. - You have created a YouTube live stream and have its stream key.
- The machine can reach YouTube's ingest endpoint.
Create an environment file first:
sudo install -d -m 0750 /etc/stream
sudo nano /etc/stream/ffmpeg.env
Put the key in that file, without committing it to a shell history or a public repository:
YOUTUBE_STREAM_KEY=replace-this-with-your-key
Then restrict access to it:
sudo chown root:stream /etc/stream/ffmpeg.env
sudo chmod 0640 /etc/stream/ffmpeg.env
Create /etc/systemd/system/youtube-loop.service with this content:
[Unit]
Description=FFmpeg YouTube loop
Wants=network-online.target
After=network-online.target
[Service]
Type=simple
User=stream
Group=stream
WorkingDirectory=/srv/stream
EnvironmentFile=/etc/stream/ffmpeg.env
ExecStart=/usr/bin/ffmpeg \
-hide_banner \
-loglevel warning \
-re \
-stream_loop -1 \
-i /srv/stream/loop.mp4 \
-c:v libx264 \
-preset veryfast \
-b:v 2500k \
-maxrate 2500k \
-bufsize 5000k \
-pix_fmt yuv420p \
-g 50 \
-c:a aac \
-b:a 128k \
-ar 44100 \
-f flv \
rtmp://a.rtmp.youtube.com/live2/${YOUTUBE_STREAM_KEY}
Restart=always
RestartSec=15
TimeoutStopSec=15
KillSignal=SIGINT
StandardOutput=journal
StandardError=journal
NoNewPrivileges=true
PrivateTmp=true
[Install]
WantedBy=multi-user.target
[Unit] describes when this service should be considered ready to start. network-online.target is a request to wait for the network's online target, not proof that YouTube is reachable. The Wants and After lines reduce a race during boot, but they cannot fix a slow DNS service, a route that is not available yet or a firewall rule.
User, Group and WorkingDirectory prevent the process from running as root and make relative paths predictable. In this example, check that the stream user can read the video and traverse every parent directory:
sudo -u stream test -r /srv/stream/loop.mp4
EnvironmentFile keeps the key out of the main command. It is still sensitive data, so protect the file and avoid pasting it into support tickets or screenshots. If the key is regenerated in YouTube Studio, update this file and restart the service.
The input options tell FFmpeg to read the file at normal playback speed and repeat it. -stream_loop -1 means an unlimited number of input loops. The output options encode video and audio into an FLV stream sent to YouTube's RTMP ingest URL. This example deliberately re-encodes instead of using -c copy, because a file that plays locally is not automatically a suitable live output. The exact resolution, frame rate, bitrate and keyframe interval should match the current requirements shown in YouTube's live encoder settings guidance.
-loglevel warning keeps routine messages out of the journal while retaining warnings and errors. During first setup, use -loglevel info temporarily so you can see more of the negotiation and input details. Put the setting back when the stream is stable if the additional output is not useful.
The final lines connect the process to the journal and request a normal interrupt when systemd stops it. NoNewPrivileges and PrivateTmp are modest hardening measures, not a complete security policy. Finally, [Install] makes the service available to start at the normal multi-user boot target when you enable it.
Reload and enable the unit only after checking the paths and permissions:
sudo systemctl daemon-reload
sudo systemctl enable --now youtube-loop.service
sudo systemctl status youtube-loop.service
A successful active (running) state means the FFmpeg process is alive. It does not prove that viewers see a healthy stream. Open the live control room and check the preview, stream health and recent errors. YouTube's official live streaming troubleshooting page is the right place to check current platform-side guidance.
Restart=always, RestartSec and the crash loop trap
Restart=always tells systemd to start the service again when the main process exits, including after a clean exit. That is useful for a loop, because a process that has stopped is not serving your channel. Restart=on-failure is narrower: it restarts after a failure but not after a successful exit. For a deliberate one-shot command, on-failure can be sensible. For this always-running loop, always makes the intent explicit.
RestartSec=15 inserts a delay before the next attempt. Without a delay, an invalid key, unreadable file or malformed command can cause a rapid series of starts. That wastes CPU, fills logs and may make diagnosis harder. The delay is not a back-off strategy: it remains the same between attempts.
A restart loop often looks like a systemd problem but is actually a stable input problem. Common examples include:
- the file path is wrong or the
streamuser cannot read the file - the stream key has been changed, revoked or copied with an extra character
- FFmpeg is missing a selected encoder or cannot open the input format
- the machine has no usable route to the ingest endpoint
- the command starts and exits because the output is rejected
Use these commands while diagnosing:
systemctl status youtube-loop.service
journalctl -u youtube-loop.service -n 100 --no-pager
journalctl -u youtube-loop.service -f
systemctl show youtube-loop.service -p NRestarts -p ExecMainStatus
Do not respond to a crash loop by increasing RestartSec and walking away. Stop the service, run the same FFmpeg command manually as the service user, read the first meaningful error and fix that cause. You can temporarily set Restart=no while troubleshooting, then restore the intended policy.
A manual stop should not be mistaken for a crash. systemctl stop youtube-loop.service is an operator action, and systemd will not keep restarting a service that you have explicitly stopped. When you restart it, systemd launches a fresh FFmpeg process and reads the current environment file.
Logging without filling the disk
The example sends standard output and standard error to the system journal. This is preferable to redirecting output to a single file that grows forever. Read the latest messages with:
journalctl -u youtube-loop.service -n 100 --no-pager
Follow new messages in real time with -f, and limit the view to a time window when investigating a particular incident:
journalctl -u youtube-loop.service --since "2 hours ago"
The journal has its own retention and storage settings. On a small DIY box, inspect the current usage before you leave the stream running:
journalctl --disk-usage
If the journal is using persistent storage, configure an appropriate SystemMaxUse or SystemKeepFree policy in the systemd-journald configuration for your distribution. Make the change according to that distribution's documentation, then restart or reload journald as instructed. Do not use an aggressive vacuum command as a substitute for retention policy, because it may remove evidence you still need while diagnosing a failure.
Keep FFmpeg at warning or error once the command is understood. If you need verbose output for a test, collect it for a defined period and then return to the quieter level. Also monitor the disk containing the journal, the video file and any temporary working space. A full disk can stop new logs, prevent file replacement and cause unrelated services to fail.
A healthy log is not necessarily a healthy broadcast. FFmpeg may remain connected while the video is frozen, the wrong input is being sent or YouTube is no longer accepting the broadcast. Include a YouTube Studio check in your monitoring routine rather than treating systemctl status as a viewer-side test.
Rotating the loop file without stopping the service
Do not write a new, large video directly over /srv/stream/loop.mp4 while FFmpeg is reading it. A partially written file can be invalid, and truncating an open file can produce confusing playback or end-of-file behaviour.
A safer file operation is to upload the new file under a temporary name, validate it, then replace the stable pathname in one rename on the same filesystem:
sudo -u stream ffprobe -v error -show_format -show_streams /srv/stream/loop.mp4.new
sudo -u stream mv /srv/stream/loop.mp4.new /srv/stream/loop.mp4
The rename is useful because readers see either the old complete file or the new complete file, rather than the upload in progress. Keep the temporary file in /srv/stream so the rename does not depend on moving data between filesystems.
There is an important limitation. An FFmpeg process that already has the input open generally continues with the file it opened; replacing the pathname does not make the current process magically reload it. With this unit's -stream_loop -1 arrangement, the safe expectation is that the running process continues using the existing input until you restart FFmpeg.
You can rotate the stored file without stopping the service, but the new content may not be used until the next service start. To apply the replacement predictably, use a controlled restart after the upload and validation:
sudo systemctl restart youtube-loop.service
That creates a short interruption while the old process exits and the new one connects. If uninterrupted handover matters, a single-file loop with a basic systemd unit is the wrong design. You would need a playback architecture designed for playlist changes or failover, and you would need to test its behaviour with the exact media and output settings.
Before replacing a file, also check its duration, audio presence, aspect ratio and licensing. A technically valid file can still be the wrong programme for your audience. The practical planning issues are similar to those in building a 24/7 channel from a single 20-minute video, particularly when repetition is part of the format.
Four things systemd cannot fix
1. A bad or changed stream key
Systemd can restart FFmpeg with the same key, but it cannot create permission to broadcast. If the key is wrong, revoked or attached to a different setup, every restart repeats the same rejection. Check the current key in YouTube Studio, replace the protected environment file and restart the service.
Do not put a new key in the unit file itself if you can avoid it. A unit is easier to share, back up and inspect when the secret is kept separately, with permissions that allow only the intended account and administrators to read it.
2. A missing, unreadable or unsuitable upload
The service cannot repair a typo in /srv/stream/loop.mp4, grant the stream user access to a parent directory or turn an incomplete upload into a valid media file. Test the file as the service user and run the exact FFmpeg command interactively before enabling automatic restarts.
It also cannot decide whether the content is appropriate for the channel. For example, a devotional loop, a study timer and a local news visual have different expectations around audio continuity, captions, updates and repetition. If the loop contains material intended for children, read the current guidance before choosing settings, as explained in Made for Kids on a 24/7 channel.
3. YouTube ending or rejecting the broadcast
A live broadcast can be affected by YouTube account settings, ingest errors, content checks or platform decisions. Restarting FFmpeg may reconnect to an output that YouTube is not accepting, or it may create a new failure if the underlying cause remains. Check the live control room and current official help rather than assuming that a running process equals a public broadcast.
A channel may also need a sensible operating plan around stream creation, scheduled events and recovery. The practical constraints are covered in how long a YouTube live stream can run, including why a 24/7 plan still needs human checks.
4. The machine, network and power
Systemd cannot provide power during an outage, repair a failed disk, increase an overloaded connection or replace a machine that has frozen at firmware level. It also cannot guarantee that a provider's route to YouTube stays available.
For a box you operate yourself, use a machine with sufficient CPU headroom, check storage health, apply security updates deliberately and arrange a way to reach it after a reboot. If the stream's value is mainly in having your laptop switched off rather than in controlling Linux directly, StreamNeo removes the need to keep this FFmpeg box running and monitored yourself: you upload the video, connect your YouTube stream key and let the hosted workflow handle the ongoing broadcast.
A maintenance checklist for a DIY box
Before leaving the stream overnight, run the whole path as the stream user. Confirm that the file opens, the selected encoders exist and the output reaches the intended YouTube broadcast. Do not enable a command that has only been tested from your administrator account.
Use this checklist during setup and after a significant change:
- Confirm the service file has been reloaded with
sudo systemctl daemon-reload. - Confirm the service is enabled for boot with
systemctl is-enabled youtube-loop.service. - Confirm the video and environment file permissions are correct.
- Check the journal for the first meaningful FFmpeg error, not only the last restart message.
- Watch the YouTube preview and stream health while the service is running.
- Test
systemctl restart youtube-loop.serviceand verify that the new process reconnects. - Test a reboot during a planned maintenance window, then check both systemd and YouTube.
- Replace the loop using a temporary file and validation, never by writing directly into the active pathname.
- Check journal disk usage and the free space where videos and temporary files live.
- Record how to regenerate the stream key and how to stop the service safely.
Keep a short change record. Note the date, the media file used, the FFmpeg version, the unit-file changes and what YouTube Studio showed. This makes it much easier to tell whether a later failure came from the host, the media, the command or the platform.
A DIY service is worthwhile when you want direct control and are prepared to inspect it. It is not worthwhile if the only reason for choosing it is the belief that Restart=always removes all operational work. The honest benefit is narrower: systemd makes boot recovery and process supervision repeatable, while you remain responsible for the parts around FFmpeg.
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
Will Restart=always reconnect the stream after every failure?
It will ask systemd to start FFmpeg again after the process exits, subject to the unit and system state. It cannot guarantee that FFmpeg reconnects successfully or that YouTube accepts the new connection. Check the journal and YouTube Studio after a failure.
Can I replace the MP4 while FFmpeg is looping it?
You can upload a replacement under a temporary name and rename it into place without corrupting the pathname. The running FFmpeg process should not be expected to reload that replacement, so restart the service when you need the new file to take effect.
Should I use -c copy to reduce CPU use?
Only if the input streams, timestamps, codecs and output container are already suitable for the destination. Re-encoding is more predictable for a general-purpose example, but it uses CPU and still needs testing on your machine. Compare the resulting stream with the current YouTube encoder guidance.
Does an active systemd service prove that my channel is live?
No. It proves that systemd believes the FFmpeg process is running. The process can be connected to the wrong destination, sending unusable media or failing at the platform side, so verify the preview and stream health in YouTube Studio.