A system-level systemd service can start FFmpeg after Linux boots, keep its command separate from an SSH session, and start it again after an unexpected process exit. It does not make an invalid stream key, unsuitable media, failed network path, or rejected YouTube broadcast work.
The reliable setup has five visible parts: a dedicated service user, a working directory, a protected environment file, an explicit FFmpeg command, and journal logging. You then check two different things: what systemd says happened to FFmpeg, and what YouTube says is happening to the broadcast.
Confirm the source and YouTube stream first
Before writing a unit file, confirm that the media works and that the YouTube broadcast exists. A restart policy is useful only after the underlying command has worked at least once.
For a file loop, check the file path and format with FFmpeg before handing it to systemd. A simple inspection command is:
ffprobe /srv/stream/media/loop.mp4
Use the actual path on your machine. Check that the file has a usable video stream, an audio stream if your output requires one, and a duration that makes sense. If the source is a camera, capture card, playlist, or another live input, test that input separately instead. A service cannot repair a missing device or a source that stops producing frames.
In YouTube Live Control Room, create or open the broadcast and copy the current stream settings. Use the RTMPS server address shown there rather than an old address copied from a forum post. YouTube describes RTMPS as RTMP carried over TLS and recommends it for encrypted ingestion in its encoder settings guidance. Keep the stream key private.
The output address normally consists of the ingest URL followed by the stream name or key, but the exact values belong to the current YouTube event. Google’s LiveStreams API documentation also describes ingestion addresses and the separate states used to report a stream’s condition. That distinction matters later: a local FFmpeg process and a healthy YouTube feed are not the same fact.
Check the channel’s ability to go live before spending time on systemd. If you have not used live streaming on this channel before, the channel live-streaming restrictions checklist is a useful preliminary check. It is better to find an account or feature restriction before diagnosing Linux logs.
Choose a service user and working directory
Create a Linux account used only for this stream, rather than running FFmpeg as root or as your everyday login. A dedicated account limits the files and devices that the process can access and makes ownership easier to understand.
For the examples below, the account and group are both named stream, the working directory is /srv/stream, and FFmpeg is installed at /usr/bin/ffmpeg. These are example values, not universal defaults. Confirm the installed binary with command -v ffmpeg and confirm that the service account can read the media.
A possible directory layout is:
/srv/stream/
├── media/
│ └── loop.mp4
└── logs/ # optional application files, not required for journal output
The service account needs traverse permission on each parent directory and read permission on the media. If the source is a device, it may also need membership of the relevant device group. Do not add broad permissions simply because a test failed. Find which path or device is inaccessible and grant the smallest useful permission.
A system-level unit normally lives in /etc/systemd/system/. That is different from a user service under ~/.config/systemd/user/: the system-level form is managed by the system manager and can start during boot without waiting for an interactive login.
The working directory is not just decoration. Relative paths in scripts, auxiliary files, or future changes are interpreted from it, and it gives you one predictable place to inspect the stream’s local assets. Use absolute paths in ExecStart wherever practical, because they make failures easier to read.
You remain responsible for the host in this arrangement. Power loss, an unavailable Internet connection, a full disk, a broken FFmpeg update, or a machine that never boots cannot be recovered by a unit file. If you are comparing a spare computer with hosted operation, the DIY streaming cost comparison covers the practical ownership trade-offs.
Protect the stream-key environment file
Do not put a real stream key directly in the unit file, a published tutorial, or a shell command that may remain in history. Store it in a root-controlled environment file with restrictive permissions and allow only the service to read it.
Create the directory and file as root:
sudo install -d -o root -g stream -m 0750 /etc/stream
sudo install -o root -g stream -m 0640 /dev/null /etc/stream/stream.env
sudoedit /etc/stream/stream.env
Put the key in the file using a simple variable assignment:
STREAM_KEY=replace-this-with-the-current-key
Do not include the example text as a real value. Avoid placing unrelated secrets in the same file. A group-readable file may be appropriate when the service account needs access, but do not make it world-readable. If your local security model allows it, root ownership with a mode that grants only the service’s group read access is a clear starting point.
The unit will refer to the file with EnvironmentFile=. systemd reads the variable and makes it available to FFmpeg. This is not a guarantee that the key can never appear in diagnostics: review your own command and logging choices, and avoid commands that print the complete expanded URL.
If YouTube issues a new key, replace the value and restart the service deliberately. A restart loop cannot discover that a key was revoked or silently substitute a valid one. After changing credentials, check both the local error and the YouTube event state.
Define the FFmpeg service and command
Create /etc/systemd/system/youtube-stream.service with a text editor. The following is a representative file-based loop:
[Unit]
Description=FFmpeg YouTube stream
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=stream
Group=stream
WorkingDirectory=/srv/stream
EnvironmentFile=/etc/stream/stream.env
ExecStart=/usr/bin/ffmpeg \
-hide_banner \
-nostdin \
-re \
-stream_loop -1 \
-i /srv/stream/media/loop.mp4 \
-c:v libx264 \
-preset veryfast \
-b:v 5M \
-maxrate 5M \
-bufsize 10M \
-g 60 \
-c:a aac \
-b:a 128k \
-ar 44100 \
-f flv \
"rtmps://YOUR_CURRENT_INGEST_HOST/YOUR_APP/${STREAM_KEY}"
Restart=on-failure
RestartSec=10
KillSignal=SIGINT
TimeoutStopSec=15
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
Replace the ingest host and application path with the current values from Live Control Room. Do not copy the placeholder URL into a working service. If YouTube presents a server URL and stream name separately, combine them exactly as instructed for that event.
The -re option makes a file input read at approximately its natural rate instead of being consumed as quickly as the machine can process it. -stream_loop -1 repeats the input file. Those options suit a file loop, not every source. A camera, desktop capture, or already-live input needs a different input section.
The codec and rate-control arguments are examples that must be matched to your file, output resolution, frame rate, CPU, and upload capacity. YouTube’s current guidance lists H.264, H.265 or HEVC, and AV1 as video options, with AAC or MP3 audio. For H.264, its page lists 1080p30 at 5 Mbps minimum and 14 Mbps recommended, and 720p30 at 3 Mbps minimum and 8 Mbps recommended. Those are YouTube’s guidance figures, not a promise that your connection or hardware can sustain them.
The example uses a 5 Mbps video rate and 128 Kbps audio, which may suit a particular 720p30 configuration but should not be treated as a universal preset. Confirm the actual frame rate and resolution, run a connection test, and watch for dropped frames. YouTube also recommends a two-second keyframe interval and says not to exceed four seconds. With a 30 fps source, -g 60 expresses a two-second GOP; change it if your frame rate differs.
After saving the file, ask systemd to reread unit definitions:
sudo systemctl daemon-reload
sudo systemctl start youtube-stream.service
sudo systemctl status youtube-stream.service
Start it manually before enabling boot-time launch. This separates command and permission errors from boot-order questions. Once the test is successful, enable it:
sudo systemctl enable youtube-stream.service
enable arranges for the service to start at boot. It does not itself start the current process unless you also use --now or run start separately.
Configure restart behaviour without overpromising
Restart=on-failure tells systemd to start FFmpeg again when the process exits unsuccessfully or is terminated in a way systemd treats as a failure. RestartSec=10 inserts a delay, which avoids an immediate tight loop when the command fails repeatedly.
This can recover from an unexpected FFmpeg exit. It cannot fix a revoked stream key, a missing file, an unsuitable codec or rate, a broken network path, or a YouTube broadcast that has ended or been rejected. If the same error happens on every launch, systemd may simply repeat that error after each delay.
The restart policy also acts on the local process lifecycle, not on the viewer experience. FFmpeg may remain active while it is unable to deliver useful media, while YouTube reports an ingest problem, or while the broadcast is not in the state you expect. Treat active (running) as evidence that the process exists, not as proof that viewers receive a healthy feed.
KillSignal=SIGINT gives FFmpeg a conventional interrupt when you stop the unit, and TimeoutStopSec gives it time to finish before systemd takes further action. The right stop behaviour depends on the input and how quickly you need the machine to shut down. Keep the unit simple until you understand the normal stop and restart sequence.
After a change, test the failure you are trying to recover from. You might stop only the FFmpeg process and observe whether systemd starts it again, but do this during a maintenance window. Do not simulate failures during an important broadcast unless a brief interruption is acceptable. Also test a clean systemctl stop: a deliberate administrative stop should not be confused with an unexpected crash.
Inspect journal logs and service status
The journal is the consistent place to inspect FFmpeg’s standard output and error because the unit sends both streams to journald. Start with the current status:
sudo systemctl status youtube-stream.service --no-pager
Then read recent entries:
sudo journalctl -u youtube-stream.service -n 100 --no-pager
For a live view while starting or restarting the service:
sudo journalctl -u youtube-stream.service -f
Look for the first meaningful error, not only the final failed line. Useful clues include an unreadable input, an invalid option, a missing encoder, permission denial, connection failure, TLS or RTMPS errors, repeated reconnects, and FFmpeg’s exit code. If the log says the input cannot be opened, changing RestartSec will not help. If it reports a connection problem, check DNS, firewall rules, the server URL, the RTMPS scheme, and the available upload path.
YouTube’s RTMPS troubleshooting guidance recommends checking that both the protocol and server are correct, and notes that port 443 can be worth trying for some SSL errors. Follow the current instructions on the official RTMPS troubleshooting page, rather than replacing a current address with a hard-coded value from an old configuration.
A service that repeatedly starts and exits is different from a service that stays active but produces no usable stream. The journal helps with the first, while YouTube’s preview and stream-health messages help with the second. Keep those investigations separate so that an apparently successful local restart does not close the diagnosis too early.
Verify the actual broadcast in Live Control Room
Open the event in YouTube Live Control Room after FFmpeg starts. Check the preview, connection or stream-health messages, and the event state. Confirm that audio and video are present, the image is moving as expected, and the broadcast is the intended event rather than a different saved configuration.
YouTube reports more than a simple running or stopped condition. Its documentation describes stream states such as active, ready, inactive, and error, together with health information and configuration issues. A local process can be active while YouTube is still waiting for data, rejecting the connection, or receiving a stream that does not meet the event’s settings.
Allow enough time to observe the normal loop and any transition relevant to your source. For a file, confirm that it reaches the end and begins again without FFmpeg exiting. For a live input, confirm that frames and audio continue after the initial connection. Watch the host’s upload use and CPU as well, because a machine that cannot encode or send consistently may leave YouTube with an unhealthy feed.
If the event is not behaving correctly, use an ordered check:
| Observation | First place to check | What it can tell you |
|---|---|---|
| The unit is inactive | systemctl status and the journal |
Whether FFmpeg stopped, failed to start, or was deliberately stopped |
| FFmpeg exits immediately | The first journal error | Whether the path, permissions, options, input, or binary is wrong |
| The unit is active but YouTube has no useful feed | Live Control Room and journal together | Whether the process exists but the remote ingest or media is failing |
| YouTube reports an ingest or configuration issue | Current event settings and RTMPS URL | Whether the key, address, protocol, or output settings need correction |
| The stream starts but later breaks | Journal, host network, and stream health | Whether a local resource, network path, or source failed over time |
For an independent loop on a small device, also consider whether the hardware is appropriate for continuous encoding. The Raspberry Pi loop-stream guide discusses the constraints of that type of host. A VPS may remove some hardware concerns, but it still leaves you responsible for the operating system, files, credentials, and service diagnosis.
Decide whether self-managed systemd is the right fit
A systemd service is a good fit when you already have a Linux host, want direct control of the media and command, and are willing to maintain the machine. You control the unit, the files, the update schedule, and the way logs are retained. You also own the consequences of power loss, network failure, disk problems, permissions, and an FFmpeg configuration that no longer matches the source.
A hosted workflow is a different trade-off for someone whose main requirement is to switch off their own computer and still have the channel continue. StreamNeo removes the need to keep a local Linux host running by taking an uploaded video, the YouTube stream key, and the continuous broadcast workflow out of that machine-maintenance routine. You still need to provide suitable media, the correct YouTube credentials, and a channel that is able to live stream.
Neither approach removes the need to verify the actual YouTube event. With systemd, you inspect the journal and Live Control Room. With a hosted workflow, you should still check the broadcast, stream health, media suitability, and account status. The important distinction is who maintains the always-on part, not whether YouTube’s own checks can be skipped.
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 systemd keep an FFmpeg stream live after a reboot?
It can start the configured service during boot when the unit is enabled and the host reaches the required network state. It cannot help if the machine does not boot, the media path is unavailable, the network is down, or YouTube rejects the connection.
Will Restart=on-failure fix a disconnected YouTube stream?
It can restart FFmpeg after an unexpected local process failure. It cannot repair a revoked key, unsuitable output, broken network path, or broadcast that has ended or been rejected. Check the journal and Live Control Room before changing the restart delay.
Is systemctl status proof that viewers can watch?
No. It shows the state of the local service and process. Confirm the preview, stream health, audio, video, and event state in YouTube Live Control Room as well.
Where should the YouTube stream key go?
Put it in a root-controlled environment file with restrictive permissions, and refer to that file from the unit with EnvironmentFile=. Do not publish the key, place it in a broadly readable unit file, or leave it in a command copied into shell history.