Skip to content
streamneo.
Setup Guides12 min read

How to Use systemd to Keep an FFmpeg YouTube Stream Running on Hetzner

Run FFmpeg under systemd on Hetzner, protect your YouTube stream key and check process status separately from YouTube stream health.

sn.
StreamNeoPublished 4 October 2026
Worth sharing?

If you run FFmpeg on a Hetzner Linux server, systemd can start it at boot and restart it when the process exits unexpectedly. That keeps the encoder process supervised, but it does not prove YouTube is receiving a usable video stream.

The reliable workflow is to prepare the source and YouTube Live ingest details, run FFmpeg in the foreground as a dedicated user, protect the stream key, then check both systemd’s journal and YouTube’s live health view. Treat the unit file below as a structure to adapt, not a verified end-to-end command.

Prepare the server and YouTube Live stream

Start by deciding what the server must read and send. Your source might be a video file, a playlist assembled locally, or another input that FFmpeg can open. Put the source in a stable location that the service account can read, and make sure the path will not change after you enable the service. A manual test that works only from your own home directory is not a good boot-time configuration.

Create a dedicated, unprivileged Linux account for the stream rather than running FFmpeg as root. Give it read access to the media and access only to the directories it needs. This limits the impact of an incorrect file path or an exposed process, and it makes permission problems easier to diagnose. Keep the service’s working directory explicit rather than relying on the shell’s current directory.

In YouTube Live Control Room, create or select the stream and obtain the ingest address and stream key shown for it. YouTube’s LiveStreams API documentation describes primary and backup ingestion addresses and the available ingest types. Use the address supplied for your stream; do not assume a remembered hostname or a URL copied from another channel is still the right one.

Where your encoder and workflow support it, consider YouTube’s RTMPS endpoint. RTMPS carries the ingest connection over TLS; Google’s RTMPS ingestion guide specifies port 443 and the requirement for TLS SNI to identify the ingest server hostname. If a connection fails, those details are useful checks, but they do not replace confirming the exact endpoint YouTube gives you.

Hetzner is the host in this guide, but do not infer a particular server size or firewall rule from the fact that a stream is 24/7. The appropriate resources depend on the input, encoding work, resolution, and other processes on the machine. Likewise, firewall configuration depends on the host and network setup. Check the current Hetzner documentation and the actual connection requirements for your selected ingest method before changing network rules. No particular plan, port rule, or server size is prescribed here.

Build the FFmpeg command for your source

Before writing a service, get the command working for the intended source and output. Decide whether FFmpeg is reading a local file or another input, what video and audio formats YouTube will accept for your stream, and what output settings suit the content and available capacity. A devotional loop with a still image and audio has different encoding demands from a live camera feed. Your settings should follow the source and current YouTube guidance, not a generic command pasted without context.

The command needs an input, any needed mapping or encoding choices, and YouTube’s ingest destination. Exact flags are intentionally not supplied here: the FFmpeg manual and the input type determine which options are appropriate, and this guide’s research did not verify a specific set of flags. In particular, do not add a purported reconnect option just because a forum answer uses it. Confirm every option against documentation for the FFmpeg build you install and test the full command outside systemd before depending on it overnight.

Use absolute paths for the FFmpeg executable and the media input. A shell may find ffmpeg through its interactive PATH, while a service starts with a different environment. Confirm the binary path on your host, and make sure the service user can read the input and any configuration files. Keep logs available so that a failure in a codec, input path, or output negotiation is visible rather than hidden behind a silent background process.

For a file-based channel, decide what should happen when the file reaches its end. Whether FFmpeg exits, loops, or uses a playlist is an application-level choice that must be tested with your own media and command. systemd can restart a process that exits, but restarting a command that reaches the end is not necessarily the same as continuous playback: it may restart from the beginning or repeatedly fail for the same reason. If playlist continuity matters, see the guidance on keeping a YouTube playlist streaming when one video fails.

Keep the stream key out of public configuration

Treat the YouTube stream key as a password. Do not place a real key in a public unit file, a code repository, a screenshot, a support post, or a command you will leave in shell history. Anyone who obtains it may be able to send a feed to your stream, so rotate it through YouTube if you believe it has been exposed.

Avoid using a plain environment variable as a secret store. The systemd execution documentation warns that environment variables are not suitable for secrets because they can be exposed through D-Bus and passed to child processes. Look at the credential mechanism supported by the systemd version installed on your host, and check its documentation before choosing how to deliver the key to FFmpeg. The right mechanism depends on both systemd support and how your FFmpeg command consumes the ingest URL.

If you must use a file-based arrangement, restrict its permissions so only the service account and the necessary administrator can read it. Check ownership and access after creating the unit, and avoid printing the complete command or URL when sharing diagnostic output. A key embedded in the output URL can appear in process listings or logs depending on how the program is invoked, so review what is visible to other users on the server. There is no reason to include a real key in an example to make the unit understandable.

Separate secret management from stream settings. The unit can describe the user, working directory, restart behaviour, and logging destination without exposing the key. Keep a private configuration outside broadly readable directories, document who can change it, and test that a service restart still reads it correctly. If your chosen method is awkward to rotate or audit, choose a better supported method before making the stream a permanent service.

Run FFmpeg in the foreground under systemd

Systemd supervises a main process. Let FFmpeg remain in the foreground so that it is that main process; do not configure it to daemonise itself behind systemd. A simple service model is intended for this kind of foreground process. If FFmpeg exits, systemd can observe the exit and apply the restart policy. If FFmpeg detaches, the relationship becomes less clear and service state may no longer describe the encoder you intended to supervise.

Here is an illustrative unit outline. The placeholders in angle brackets are explanations, not valid FFmpeg syntax. Replace them with the absolute executable path and the options, source, and key-delivery method you have tested on the target host.

[Unit]
Description=FFmpeg YouTube live stream
Wants=network-online.target
After=network-online.target

[Service]
Type=simple
User=stream
WorkingDirectory=/srv/stream
ExecStart=/usr/bin/ffmpeg <input-and-encoding-options> <youtube-ingest-url>
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

This shows the shape of a service, not a complete command. User should match the unprivileged account you created, and the working directory should be a place that account can access. Confirm the location of FFmpeg rather than assuming /usr/bin/ffmpeg on every installation. Substitute a safe way to provide the stream key rather than placing a real key in the unit. The example’s restart delay is illustrative; choose a considered delay for your failure pattern rather than treating it as a universal setting.

Save a system service unit in the location appropriate for your distribution, then ask systemd to reload unit definitions, enable the service at boot, and start it. The usual administrative workflow involves systemctl daemon-reload, systemctl enable <unit-name>, and systemctl start <unit-name>, substituting the actual unit name. Check your distribution’s systemd guidance and permissions; do not paste the placeholder name literally. Test a controlled stop and start before you rely on the next reboot to reveal mistakes.

If you are still choosing how to arrange a continuous channel, compare this hands-on approach with ways to loop videos on an unlisted YouTube livestream. That is a different operational question from keeping a particular FFmpeg process under systemd. For this setup, the server remains yours to administer: updates, storage, logs, and the stream command all need attention.

Configure and test process-failure restarts

A long-running process commonly uses Restart=on-failure so systemd tries to recover after a non-success exit or certain failure conditions. The systemd service documentation recommends this policy for long-running services. A restart delay such as RestartSec= prevents an immediate retry from becoming a tight loop, but it does not repair a bad command, unavailable input, or rejected stream key.

Test what the policy actually does. Start the service, observe it, then perform a deliberate test in a maintenance window by stopping or terminating the process in a way that exercises the configured failure behaviour. Confirm that the service state changes and that a subsequent attempt appears in the journal. Do not test by exposing a real key or breaking production content while an audience is relying on it. After the test, verify that the new process can read the source and reconnect to YouTube.

Systemd also applies start-rate limits. If a service repeatedly fails, it may stop making attempts until the relevant limit is reset or the failure is addressed. This is protective: an invalid path or permanently rejected configuration should not produce an unbounded, rapid restart loop. When retries cease, read the journal and manager messages, find the underlying problem, correct it, then follow the systemd procedure for resetting the failed state if required. Avoid raising limits just to conceal recurring errors.

A restart policy covers process failure, not every kind of stream failure. FFmpeg might remain alive while its input stalls or while the output no longer reaches YouTube. A process can also be active while producing video or audio that YouTube flags as invalid. If you need detection of such application-level conditions, define a separate monitoring and alerting approach; do not assume that a systemd restart directive can infer what YouTube is receiving.

Check service status and YouTube stream health separately

Use systemd to answer local questions: is the unit loaded, is its main process active, and what did the program report recently? systemctl status <unit-name> gives a concise view of the unit. journalctl -u <unit-name> shows its logs; add an appropriate time range or follow mode when you are diagnosing a live incident. FFmpeg’s stderr is often the useful evidence for a bad file path, unreadable source, unsupported encoding choice, or failed connection.

Then check YouTube Live Control Room for the receiving stream and its health. YouTube’s health status reference describes warnings related to matters such as bitrate, codec, frame rate, GOP, and insufficient video. A green-looking local process state cannot substitute for this check. The YouTube LiveStreams resource also distinguishes ingest states such as active, inactive, error, and ready; use YouTube’s own view to understand what has arrived rather than inferring reception from a running PID.

What you see What it tells you Next check
Unit is inactive or failed The local process did not remain available, or systemd did not start it Read the unit status and recent journal entries; check paths, permissions, and FFmpeg errors
Unit is active, but YouTube shows no incoming feed FFmpeg may still be alive without a valid connection or usable output Check the ingest URL and key delivery, network reachability, and FFmpeg output messages
YouTube receives video but reports a health warning Data is arriving, but one or more characteristics may not meet expectations Read the specific YouTube warning and review the source and encoding settings
Restarts have stopped after repeated failures A systemd start-rate limit may have been reached Read manager and unit logs, fix the cause, then reset and start according to systemd guidance

This division makes diagnosis faster. When the unit fails, repair the local service first. When the unit stays active but YouTube sees nothing, investigate input, encoding, ingest details, and network path instead of simply increasing restart frequency. When YouTube reports a health warning, identify its category and test one change at a time so you can tell whether the incoming stream improved.

For common encoder problems, the reasoning overlaps with the checks in troubleshooting an overloaded YouTube Live encoder, even though that article focuses on OBS rather than this Linux service. The broader principle is the same: distinguish local encoding behaviour from the state YouTube reports. If you want to plan the viewer-facing side of a continuous channel as well, production tips for YouTube live streams cover a different part of the job.

For a small operator who wants a file to keep broadcasting without leaving a personal computer on, StreamNeo removes the specific burden of maintaining this FFmpeg service and its host; it is YouTube-only and does not replace a setup where you need to send a live encoder feed or administer the server yourself.

Once you have decided whether you want to maintain the server workflow yourself, compare the operating options on the pricing page. When the file and channel are ready, start free — 24-hour trial, no card.

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 an active systemd service mean YouTube is receiving my stream?

No. It means systemd considers the service process active, not that YouTube has received a valid video and audio feed. Check the Live Control Room or the relevant YouTube health information as well as the local journal.

Should I use RTMP or RTMPS?

Use the ingest details YouTube provides for your stream, and prefer RTMPS where your encoder and workflow support it. YouTube documents RTMPS as a TLS-protected ingest path with port 443 and SNI hostname requirements, so confirm those details if connection errors occur.

Why did systemd stop retrying FFmpeg?

Repeated failures can reach systemd’s start-rate limit, which constrains further attempts. Read the service and manager logs, fix the cause of failure, then use the documented reset and restart process rather than creating a rapid, unlimited retry loop.

Can I rely on Restart=on-failure to fix a stalled stream?

Not by itself. It can respond when the process exits or meets certain failure conditions, but an active FFmpeg process may still be stalled or sending data YouTube considers unhealthy. Monitor both the service and the receiving status at YouTube.

YOU’VE REACHED THE END

Keep the ideas coming.

More guides, useful tools and a little help for your next broadcast.

Back to the journal ↗
YOUR NEXT READ

A little more to explore.

More Setup Guides guides ↗ · All topics ↗