--- title: "How to Set Up an Nginx RTMP Streaming Server on Ubuntu 24.04" description: "Build a self-hosted live streaming server on Ubuntu 24.04 with Nginx and the RTMP module: accept streams from OBS, protect them with a key and play them as HLS." category: "Use Cases" language: en source: https://cubepath.com/docs/use-cases/streaming-server-with-nginx-rtmp updated: 2026-09-25 --- # How to Set Up an Nginx RTMP Streaming Server on Ubuntu 24.04 The RTMP module for Nginx turns a regular Nginx web server into a live streaming server: it accepts video from an encoder such as OBS Studio over RTMP, and repackages it as HLS so viewers can watch in any browser. In this tutorial you will install Nginx with the RTMP module from the Ubuntu 24.04 repositories, protect publishing with a stream key, serve the stream as HLS, and test it end to end with FFmpeg. ## Prerequisites To follow this guide you need: - A server running Ubuntu 24.04 LTS, for example a CubePath VPS, with at least 1 vCPU and 1 GB of RAM. Nginx only repackages the stream here (no transcoding), so CPU usage stays low. - A non-root user with `sudo` privileges. - Enough upstream bandwidth: roughly the stream bitrate multiplied by the number of concurrent viewers (a 3 Mbps stream watched by 50 people needs about 150 Mbps). - OBS Studio or FFmpeg on the machine you will stream from. Throughout the guide, replace `your_server_ip` with your server's public IP address. ## Step 1 - Installing Nginx and the RTMP module Ubuntu packages the RTMP module as `libnginx-mod-rtmp`, so there is no need to compile Nginx from source. Installing it also pulls in Nginx itself. Install FFmpeg too, which you will use to send a test stream from the server: ```bash sudo apt update sudo apt install nginx libnginx-mod-rtmp ffmpeg ``` The package enables the module by dropping a `load_module` line into `/etc/nginx/modules-enabled/`. Confirm it is there: ```bash ls /etc/nginx/modules-enabled/ | grep rtmp ``` ```output 50-mod-rtmp.conf ``` Check that Nginx is running: ```bash systemctl status nginx --no-pager ``` The output should show `active (running)`. ## Step 2 - Creating the HLS directory The RTMP module writes HLS playlists (`.m3u8`) and video segments (`.ts`) to disk, and the regular Nginx HTTP server then serves those files to viewers. Create a directory for them owned by `www-data`, the user Nginx workers run as on Ubuntu: ```bash sudo mkdir -p /var/www/hls sudo chown www-data:www-data /var/www/hls ``` ## Step 3 - Configuring the RTMP server The `rtmp` block must live in the main context of the Nginx configuration, at the same level as the `http` block. Files in `/etc/nginx/conf.d/` and `/etc/nginx/sites-enabled/` are included inside `http`, so they cannot hold it. Instead, create a dedicated file and include it from `nginx.conf`. Create the RTMP configuration file: ```bash sudo nano /etc/nginx/rtmp.conf ``` Add the following content: ```nginx rtmp { server { listen 1935; chunk_size 4096; application live { live on; record off; # Only allow publishing after the key check succeeds notify_method get; on_publish http://127.0.0.1/rtmp-auth; # Viewers watch over HLS, not RTMP deny play all; hls on; hls_path /var/www/hls; hls_fragment 3s; hls_playlist_length 60s; } } } ``` What these directives do: - `application live` defines the path encoders publish to: `rtmp://your_server_ip/live/`. - `on_publish` makes Nginx call an HTTP URL before accepting a stream. A `2xx` response allows it, anything else rejects it. `notify_method get` sends the stream name and query arguments as URL parameters, which makes them easy to check in Nginx. - `deny play all` blocks direct RTMP playback, so the only way to watch is the HLS output. - `hls_fragment` and `hls_playlist_length` control segment length and how much of the stream the playlist keeps. Shorter fragments lower latency but create more requests. Now append an `include` line for this file at the end of `/etc/nginx/nginx.conf`, outside the `http` block: ```bash echo 'include /etc/nginx/rtmp.conf;' | sudo tee -a /etc/nginx/nginx.conf ``` ## Step 4 - Serving HLS and checking the stream key Next, create an HTTP server block that serves the HLS files and answers the `on_publish` check. Replace the default site with it: ```bash sudo nano /etc/nginx/sites-available/streaming ``` Paste the following, replacing `your_secret_key` with a long random string (you can generate one with `openssl rand -hex 16`): ```nginx server { listen 80 default_server; listen [::]:80 default_server; server_name _; root /var/www/html; location /hls { types { application/vnd.apple.mpegurl m3u8; video/mp2t ts; } root /var/www; add_header Cache-Control no-cache; add_header Access-Control-Allow-Origin *; } # Called by the RTMP module before accepting a publisher location = /rtmp-auth { allow 127.0.0.1; deny all; if ($arg_key = "your_secret_key") { return 204; } return 403; } } ``` The `/hls` location serves files from `/var/www/hls` with the correct MIME types and disables caching of the playlist, which changes every few seconds. The CORS header lets players on other domains load the stream. The `/rtmp-auth` location is only reachable from the server itself and returns `204` when the `key` argument matches. Enable the new site and disable the default one: ```bash sudo ln -s /etc/nginx/sites-available/streaming /etc/nginx/sites-enabled/ sudo rm /etc/nginx/sites-enabled/default ``` Test the configuration and reload Nginx: ```bash sudo nginx -t sudo systemctl reload nginx ``` ```output nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful ``` Confirm that Nginx is now listening on the RTMP port: ```bash sudo ss -tlnp | grep 1935 ``` ```output LISTEN 0 511 0.0.0.0:1935 0.0.0.0:* users:(("nginx",pid=4321,fd=8),...) ``` ## Step 5 - Opening the firewall Open SSH (so you don't lock yourself out), HTTP for viewers and RTMP port 1935 for encoders: ```bash sudo ufw allow OpenSSH sudo ufw allow 80/tcp sudo ufw allow 1935/tcp sudo ufw enable ``` Check the result: ```bash sudo ufw status ``` ```output Status: active To Action From -- ------ ---- OpenSSH ALLOW Anywhere 80/tcp ALLOW Anywhere 1935/tcp ALLOW Anywhere ... ``` > Tip: If you always stream from the same location, restrict port 1935 to that address with `sudo ufw allow from your_home_ip to any port 1935 proto tcp` instead. ## Step 6 - Sending a test stream Before configuring OBS, verify the whole chain with FFmpeg on the server. This command generates a test pattern with a tone and publishes it to the stream `test`, passing the key as a query argument: ```bash ffmpeg -re -f lavfi -i testsrc=size=1280x720:rate=30 -f lavfi -i sine=frequency=1000 \ -c:v libx264 -preset veryfast -b:v 2500k -g 60 -c:a aac -b:a 128k \ -f flv "rtmp://127.0.0.1/live/test?key=your_secret_key" ``` Leave it running and open a second SSH session. After a few seconds the playlist and segments appear: ```bash ls /var/www/hls ``` ```output test-0.ts test-1.ts test-2.ts test.m3u8 ``` Fetch the playlist over HTTP: ```bash curl http://127.0.0.1/hls/test.m3u8 ``` ```output #EXTM3U #EXT-X-VERSION:3 #EXT-X-MEDIA-SEQUENCE:0 #EXT-X-TARGETDURATION:3 ... ``` Now check that the key is enforced. Stop FFmpeg with `Ctrl+C` and run it again with a wrong key. FFmpeg should fail to publish, and the Nginx error log records the rejection: ```bash sudo tail -n 5 /var/log/nginx/error.log ``` ```output ... notify: HTTP retcode: 4xx ... ``` ## Step 7 - Streaming from OBS Studio In OBS Studio, open **Settings > Stream** and set: - **Service:** Custom - **Server:** `rtmp://your_server_ip/live` - **Stream Key:** `mystream?key=your_secret_key` The part before `?` is the stream name and becomes the file name of the playlist. Under **Settings > Output**, a keyframe interval of 2 seconds works well with 3-second HLS fragments. Click **Start Streaming**. The stream is available at: ```text http://your_server_ip/hls/mystream.m3u8 ``` Open that URL in VLC (**Media > Open Network Stream**) or in Safari, which plays HLS natively. Expect 10 to 20 seconds of delay compared to the source, which is normal for HLS. ## Step 8 - Adding a browser player Chrome and Firefox don't play HLS natively, so a small page with the `hls.js` library is needed. Create it in the web root: ```bash sudo nano /var/www/html/index.html ``` ```html Live stream ``` Visit `http://your_server_ip/` while OBS is streaming and the video starts playing. Browsers only autoplay muted video, which is why the `muted` attribute is set; viewers can unmute from the controls. ## Step 9 - Recording and restreaming (optional) The RTMP module can also save each broadcast to disk and forward it to other platforms at the same time. To record, create a directory for the recordings: ```bash sudo mkdir -p /var/www/recordings sudo chown www-data:www-data /var/www/recordings ``` Then edit `/etc/nginx/rtmp.conf` and, inside `application live`, replace `record off;` with: ```nginx record all; record_path /var/www/recordings; record_unique on; ``` To forward the stream to YouTube Live as well, add a `push` line in the same block with your YouTube stream key: ```nginx push rtmp://a.rtmp.youtube.com/live2/your_youtube_stream_key; ``` Reload Nginx after each change: ```bash sudo nginx -t && sudo systemctl reload nginx ``` Recordings are written as `.flv` files. Convert one to MP4 without re-encoding with: ```bash ffmpeg -i /var/www/recordings/mystream-1759000000.flv -c copy mystream.mp4 ``` > Note: `push` only supports plain RTMP destinations. Platforms that require RTMPS need an extra relay such as FFmpeg or stunnel. ## Troubleshooting - **OBS says "Failed to connect to server".** Check that port 1935 is open in UFW and that `ss -tlnp` shows Nginx on 1935. If it is missing, the `include /etc/nginx/rtmp.conf;` line is missing or ended up inside the `http` block. - **OBS connects and disconnects immediately.** The key check failed. Look for `notify: HTTP retcode` in `/var/log/nginx/error.log` and make sure the stream key in OBS is `name?key=your_secret_key`. - **`nginx -t` fails with `unknown directive "rtmp"`.** The module is not loaded. Reinstall `libnginx-mod-rtmp` and check `/etc/nginx/modules-enabled/`. - **The `.m3u8` returns 404.** The stream name in the URL must match the one in OBS, and `/var/www/hls` must be writable by `www-data`. - **Playback stalls or buffers.** Lower the bitrate in OBS, and make sure the keyframe interval is not longer than `hls_fragment`. ## Conclusion You now have a self-hosted live streaming server that accepts authenticated RTMP streams from OBS, serves them as HLS to browsers, and can optionally record and restream them. Good next steps are to point a domain at the server and add HTTPS to the HLS site with Certbot, add FFmpeg-based transcoding to offer several quality levels, and put a CDN in front of `/hls` if you expect a large audience.