Owncast is an open source, self-hosted live streaming server with built-in chat. You send it an RTMP stream from OBS Studio, it transcodes the video with FFmpeg into HLS, and viewers watch it in a web page you control, without Twitch or YouTube in between.
In this tutorial you will install Owncast on Ubuntu 24.04, run it as an unprivileged systemd service, put it behind Nginx with a Let's Encrypt certificate, and send your first stream from OBS Studio.
Prerequisites
To follow this tutorial, you will need:
- A server running Ubuntu 24.04, for example a CubePath VPS, with at least 2 vCPUs and 2 GB of RAM. Transcoding is CPU intensive: plan roughly one extra vCPU for every additional video quality you offer.
- A non-root user with
sudoprivileges and UFW enabled. - A domain name with an
Arecord pointing to your server's IP address. This tutorial usesyour_domainas a placeholder. - OBS Studio installed on the computer you will stream from.
Step 1 - Installing FFmpeg and Nginx
Owncast uses FFmpeg to transcode the incoming stream, and Nginx will terminate HTTPS in front of it. Both are in the Ubuntu repositories:
sudo apt update
sudo apt install -y ffmpeg nginx unzip
Confirm that FFmpeg is available:
ffmpeg -version | head -n 1
ffmpeg version 6.1.1-3ubuntu5 Copyright (c) 2000-2023 the FFmpeg developers
Step 2 - Downloading Owncast
Owncast ships as a single binary. Create a dedicated system user that owns the installation, so the service never runs as root:
sudo useradd --system --create-home --home-dir /opt/owncast --shell /usr/sbin/nologin owncast
Look up the download URL of the latest release from the GitHub API and download it:
cd /tmp
OWNCAST_URL=$(curl -s https://api.github.com/repos/owncast/owncast/releases/latest | grep -o 'https://[^"]*linux-64bit.zip')
echo "$OWNCAST_URL"
curl -LO "$OWNCAST_URL"
NoteOn an ARM64 server, replace
linux-64bit.zipwithlinux-arm64.zip.
Extract the archive into /opt/owncast and give ownership to the owncast user:
sudo unzip -o /tmp/owncast-*-linux-64bit.zip -d /opt/owncast
sudo chown -R owncast:owncast /opt/owncast
Check that the binary is in place, owned by owncast and executable (-rwxr-xr-x):
ls -l /opt/owncast/owncast
Step 3 - Running Owncast as a systemd service
Owncast listens on port 8080 for the web interface and on port 1935 for RTMP. Because Nginx will serve the web interface publicly, bind the web server to 127.0.0.1 so port 8080 is never exposed directly.
Create the unit file:
sudo nano /etc/systemd/system/owncast.service
Add the following content:
[Unit]
Description=Owncast live streaming server
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=owncast
Group=owncast
WorkingDirectory=/opt/owncast
ExecStart=/opt/owncast/owncast -webserverip 127.0.0.1 -webserverport 8080 -rtmpport 1935
Restart=on-failure
RestartSec=5
NoNewPrivileges=true
PrivateTmp=true
[Install]
WantedBy=multi-user.target
Owncast stores its database and HLS segments in data/ inside the working directory, which is why WorkingDirectory points to /opt/owncast.
Reload systemd and start the service:
sudo systemctl daemon-reload
sudo systemctl enable --now owncast
Check that it is running and listening on the expected addresses:
sudo systemctl status owncast --no-pager
sudo ss -tlnp | grep owncast
LISTEN 0 4096 127.0.0.1:8080 0.0.0.0:* users:(("owncast",pid=2211,fd=9))
LISTEN 0 4096 *:1935 *:* users:(("owncast",pid=2211,fd=8))
You can also query the status API locally:
curl -s http://127.0.0.1:8080/api/status
The JSON response includes "online":false because nothing is streaming yet.
Step 4 - Configuring Nginx and HTTPS
Create an Nginx server block that proxies requests to Owncast. The Upgrade and Connection headers are required for the chat, which uses WebSockets.
sudo nano /etc/nginx/sites-available/owncast
server {
listen 80;
listen [::]:80;
server_name your_domain;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
Enable the site, test the configuration and reload Nginx:
sudo ln -s /etc/nginx/sites-available/owncast /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
Open the firewall for HTTP, HTTPS and RTMP. Port 8080 stays closed:
sudo ufw allow 'Nginx Full'
sudo ufw allow 1935/tcp
sudo ufw status
Now request a Let's Encrypt certificate. Certbot's Nginx plugin adds the listen 443 ssl block and the HTTP to HTTPS redirect for you:
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d your_domain
When Certbot finishes, open https://your_domain in a browser. You should see the Owncast page with an offline message.
Step 5 - Securing the admin panel
The admin panel is at https://your_domain/admin. On a fresh install, the username is admin and both the admin password and the default stream key are abc123. Change them before you do anything else:
- Log in to
https://your_domain/adminwithadmin/abc123. - Go to Configuration > Server Setup and set a strong admin password.
- In the Stream Keys section of the same page, delete the default key and create a new, long random one. You can generate one on the server with
openssl rand -hex 24. - Go to Configuration > General, set the Server URL to
https://your_domain, and fill in the server name, description and logo.
WarningAnyone who knows the stream key can broadcast on your server. Treat it like a password.
Step 6 - Streaming from OBS Studio
In OBS Studio, open Settings > Stream and set:
- Service: Custom...
- Server:
rtmp://your_domain/live - Stream Key: the key you created in the previous step
Then open Settings > Output, switch Output Mode to Advanced, and use settings that Owncast can transcode efficiently:
| Setting | Recommended value |
|---|---|
| Encoder | x264, or NVENC H.264 if you have an NVIDIA GPU |
| Rate control | CBR |
| Bitrate | 2500-6000 Kbps |
| Keyframe interval | 2 s |
| Resolution | 1280x720 or 1920x1080 |
| FPS | 30 |
Click Start Streaming. Within a few seconds the service log should show the incoming stream:
sudo journalctl -u owncast -f
Reload https://your_domain: the player starts after a short buffer and curl -s http://127.0.0.1:8080/api/status now reports "online":true.
NoteRTMP is not encrypted. The stream key travels in clear text between OBS and the server, so rotate it if you stream from untrusted networks.
Step 7 - Tuning video quality and latency
In the admin panel, open Video to control the qualities (output variants) that Owncast produces. Each variant is a separate FFmpeg encode, so it costs CPU.
A sensible starting point for a 2 to 4 vCPU server is two variants:
| Variant | Resolution | Video bitrate | Framerate |
|---|---|---|---|
| High | 1280x720 | 3000 Kbps | 30 |
| Low | 854x480 | 800 Kbps | 30 |
If the CPU cannot keep up, enable video passthrough on the high variant. Owncast then relays the video from OBS without re-encoding it, at the cost of sending your OBS bitrate to every viewer on that quality.
The latency setting on the same page trades delay for stability: lower latency means smaller buffers and more rebuffering on poor connections. Leave the default until you have measured your viewers' experience.
Watch CPU usage while you stream to check that the server has headroom:
top -o %CPU
If ffmpeg processes stay close to 100% of all cores, remove a variant or lower its resolution.
Step 8 - Offloading video to S3-compatible storage (optional)
By default, Owncast serves the HLS video segments itself, so every viewer's bandwidth goes through your server. For larger audiences, Owncast can upload the segments to S3-compatible object storage (AWS S3, Wasabi, Backblaze B2, MinIO) and let viewers download them from there or from a CDN in front of the bucket.
In the admin panel, go to Configuration > Storage (called S3 in some versions), enable it, and fill in the endpoint, access key, secret, bucket and region of your provider. For MinIO and some other providers, enable the path-style option. Start a test stream and confirm that new objects appear in the bucket.
NoteThis feature offloads live segments for delivery. Owncast does not record streams to the bucket as permanent VOD files.
Troubleshooting
OBS says "Failed to connect to server". Check that Owncast is listening on RTMP and that the port is open:
sudo ss -tlnp | grep 1935
sudo ufw status | grep 1935
If both look correct, check the key: a wrong stream key is logged by Owncast and the connection is rejected.
sudo journalctl -u owncast -n 50 --no-pager
The page loads but chat does not connect. The WebSocket headers are missing from the Nginx configuration. Compare your server block with the one in Step 4, then run sudo nginx -t && sudo systemctl reload nginx.
Viewers see constant buffering. The server is usually short on CPU or upload bandwidth. Check top during a stream, reduce the number of variants, or enable passthrough as described in Step 7.
The service does not start after an upgrade. Look at the last lines of the journal. Permission errors mean files in /opt/owncast are not owned by the owncast user: run sudo chown -R owncast:owncast /opt/owncast and restart the service.
Conclusion
You now have Owncast running as a systemd service on Ubuntu 24.04, served over HTTPS by Nginx, and receiving streams from OBS Studio. Your stream, chat and viewer data stay on infrastructure you control.
From here you can customize the page appearance and chat rules in the admin panel, put a CDN in front of the S3 bucket to handle large audiences, and back up /opt/owncast/data regularly so you keep your configuration and chat history.
