How to configure Nginx RTMP for live streaming from OBS

Technical guide to install the Nginx RTMP module on Ubuntu, configure the stream key, publish from OBS and generate HLS for browser distribution.

A self-hosted streaming server means full control: RTMP ingestion from OBS, HLS distribution to the browser, the ability to record everything to disk and zero dependency on third-party platforms with opaque content rules. The nginx-rtmp module solves this in a single piece — it receives the stream from the encoder and packages it into HLS fragments in the same process.

This tutorial covers installing Nginx with the RTMP module compiled from source on Ubuntu 24.04 LTS, configuring a live application with key-based authentication, generating the HLS manifest for distribution and firewall rules to close down the attack surface. By the end, you publish from OBS and watch in the browser via HTML5.

Estimated time: 30-40 minutes on a clean VPS. Execution cost: ~80 MB of build packages + ~30 MB for the final Nginx binary.

Prerequisites

Before you start

You need Ubuntu 24.04 LTS with sudo access, at least 2 GB of RAM (the compile uses ~1 GB at peak) and ports 80, 443 and 1935 open in the provider’s external firewall. SSH connected and working.

Operating system Ubuntu 24.04 LTS
RTMP port 1935/tcp
HLS port 80/tcp or 443/tcp
Tested encoder OBS Studio 30+

Packages we are going to install for compilation: build-essential, libpcre3-dev, libssl-dev, zlib1g-dev and git. All from the official repository — no external PPA.

Install build dependencies

Before downloading the source code, prepare the system with the libraries Nginx needs to compile with SSL/TLS, regex and gzip compression support.

01

Update the APT package index:

sudo apt update

This command syncs the lists of available packages. Required before installing anything new on a server that has been idle.

02

Install the compiler and the development libraries:

sudo apt install -y build-essential libpcre3-dev libssl-dev zlib1g-dev git wget

build-essential brings gcc and make. libpcre3-dev is required for the Nginx regex module, libssl-dev for HTTPS and zlib1g-dev for gzip. Around ~250 MB installed in total.

Download the Nginx source and the RTMP module

The nginx-rtmp-module is maintained as a fork — the original by Arut has been archived since 2017. For production in 2026, use the arut/nginx-rtmp-module fork which still works for classic features, or sergey-dryabzhinsky/nginx-rtmp-module which keeps receiving patches.

03

Create a working directory and download the stable Nginx release:

mkdir -p ~/build && cd ~/build
wget https://nginx.org/download/nginx-1.26.2.tar.gz
tar -xzvf nginx-1.26.2.tar.gz

The 1.26.x version is the current stable line. Avoid mainline (1.27.x) in production unless you need a specific feature.

04

Clone the RTMP module:

cd ~/build
git clone https://github.com/sergey-dryabzhinsky/nginx-rtmp-module.git

This fork maintains compatibility with modern Nginx and ships bugfixes that the archived original never received.

Compile and install Nginx with the RTMP module

Compilation takes 4-5 minutes on a modern vCPU. The configure script accepts dozens of flags — below we keep the minimum set required for a functional HTTPS streaming server.

05

Configure the build pointing to the RTMP module:

cd ~/build/nginx-1.26.2
./configure \
  --prefix=/etc/nginx \
  --sbin-path=/usr/sbin/nginx \
  --conf-path=/etc/nginx/nginx.conf \
  --pid-path=/var/run/nginx.pid \
  --with-http_ssl_module \
  --with-http_v2_module \
  --add-module=../nginx-rtmp-module

If any --with-* complains about a missing library, install the corresponding -dev package via apt and re-run configure.

06

Compile and install:

make -j$(nproc)
sudo make install

-j$(nproc) parallelizes the compilation across the available cores. On a single vCPU it takes ~7 minutes; on 4 vCPUs, ~2 minutes.

07

Create the systemd service to manage Nginx:

sudo tee /etc/systemd/system/nginx.service > /dev/null <<'EOF'
[Unit]
Description=Nginx HTTP and RTMP server
After=network.target

[Service]
Type=forking
PIDFile=/var/run/nginx.pid
ExecStartPre=/usr/sbin/nginx -t
ExecStart=/usr/sbin/nginx
ExecReload=/usr/sbin/nginx -s reload
ExecStop=/usr/sbin/nginx -s quit
PrivateTmp=true

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable nginx

ExecStartPre validates the configuration before bringing it up — avoids a broken service after an edit.

Configure the RTMP live application

Now we edit nginx.conf to add the rtmp block (outside the http block, at the root level) and configure a live application that accepts the OBS publication and generates HLS fragments.

08

Create the directory where HLS fragments will be written:

sudo mkdir -p /var/www/hls
sudo chown -R nobody:nogroup /var/www/hls

Nginx, without a custom user configured, runs as nobody. Adjust if you set user www-data in nginx.conf.

09

Replace /etc/nginx/nginx.conf with a configuration that combines HTTP + RTMP:

worker_processes auto;
events {
    worker_connections 1024;
}

rtmp {
    server {
        listen 1935;
        chunk_size 4096;

        application live {
            live on;
            record off;

            hls on;
            hls_path /var/www/hls;
            hls_fragment 3s;
            hls_playlist_length 60s;

            allow publish 127.0.0.1;
            allow publish 198.51.100.0/24;
            deny publish all;
        }
    }
}

http {
    include mime.types;
    default_type application/octet-stream;
    sendfile on;
    keepalive_timeout 65;

    server {
        listen 80;
        server_name _;

        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 *;
        }

        location /stat {
            rtmp_stat all;
            rtmp_stat_stylesheet stat.xsl;
        }

        location /stat.xsl {
            root /var/www;
        }
    }
}

Replace 198.51.100.0/24 with your actual source IP/network. The allow publish block is the first line of defense — without it, anyone with your RTMP URL can publish.

The rtmp block stays outside http

Classic mistake: putting the rtmp block inside http {}. It is top-level, parallel to http. If you paste it inside, Nginx will fail nginx -t with a confusing message about an unknown directive.

10

Validate the syntax and start the service:

sudo nginx -t
sudo systemctl start nginx

nginx -t should respond with configuration file ... test is successful. Any error here points to the exact line — fix it before continuing.

Open ports in the firewall

Without firewall rules, port 1935 is exposed to the whole internet. UFW on Ubuntu handles this in a simple way.

11

Enable UFW and open the required ports:

sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 1935/tcp
sudo ufw enable

Confirm with sudo ufw status numbered that the 4 rules show up before you disconnect from SSH.

Do not forget SSH before enable

ufw enable applies the default-deny policy on inbound traffic immediately. If you forget to allow OpenSSH first, you lose the session and have to use the provider’s serial console to unblock yourself.

Publish from OBS

With the server ready, configure OBS for ingestion.

12

Open OBS → Settings → Stream and fill in:

Service Custom
Server rtmp://YOUR-IP/live
Stream key my-stream-001

The “key” is the name that shows up in the HLS fragments and in the Nginx-RTMP logs. It can be any ASCII string without spaces.

13

Click “Start Streaming” in OBS. OBS connects to rtmp://YOUR-IP/live and starts publishing the stream with the name my-stream-001.

Verify the broadcast

Check that HLS fragments are being generated and play in a web player.

14

List the files in /var/www/hls:

ls -la /var/www/hls/

Expected output, ~3 seconds after starting to publish:

my-stream-001-0.ts
my-stream-001-1.ts
my-stream-001.m3u8

The .ts files are MPEG-TS fragments of ~3 seconds each. The .m3u8 is the manifest that points to the current fragments.

15

Test it in the browser via https://hls-js.netlify.app/demo/ or any player that accepts HLS. Paste http://YOUR-IP/hls/my-stream-001.m3u8 and hit play.

For a quick test without a web player, use ffplay:

ffplay http://YOUR-IP/hls/my-stream-001.m3u8

Expected end-to-end latency from OBS to the player is 9-15 seconds with the default configuration above (hls_fragment 3s × buffer of 3 fragments on the player).

Troubleshooting

OBS connects but nothing shows up in /var/www/hls

Check the directory permission:

sudo -u nobody touch /var/www/hls/test && rm /var/www/hls/test

If it complains “Permission denied”, reapply chown -R nobody:nogroup /var/www/hls.

”Failed to connect to server” error in OBS

Check if port 1935 is reachable from outside:

nc -zv YOUR-IP 1935

If it fails, check UFW (sudo ufw status) and the VPS provider’s firewall — some have a separate external firewall.

Player loads the m3u8 but throws “no supported source”

CORS missing or wrong MIME type. Confirm that the .m3u8 response carries Content-Type: application/vnd.apple.mpegurl and Access-Control-Allow-Origin: *:

curl -I http://YOUR-IP/hls/my-stream-001.m3u8

Next steps

You now have a working RTMP server converting to HLS. From here you can go deeper in several directions:

  • Add HTTPS with Let’s Encrypt on the HTTP server (Certbot works normally — RTMP itself does not get TLS directly, but HLS does).
  • Implement authentication via on_publish pointing to your own endpoint that validates tokens against a database.
  • Enable automatic recording with record all + record_path /var/www/recordings to archive everything in FLV.
  • Transcode to multiple bitrates with exec ffmpeg inside the application block — required for decent ABR (adaptive bitrate).
  • Swap HLS for LL-HLS (hls_fragment 1s) or WebRTC if you need sub-second latency.

If you are putting this into production, a Hostini streaming VPS already ships with dedicated bandwidth and RTMP/1935 ports open by default — no need to open a ticket for the external firewall.

Frequently asked questions

Why do I need to compile Nginx from scratch instead of using the apt package?

The nginx-rtmp module is not dynamic in stable Nginx on Ubuntu — it has to be linked at compile time via `--add-module`. The `libnginx-mod-rtmp` package exists in some mirrors, but it tends to stay stuck on old module versions (1.2.1 from 2017). Compiling takes 4-5 minutes and gives you full control over the version.

What is the real latency of an RTMP stream converted to HLS?

Between 8 and 30 seconds with default settings, depending on `hls_fragment` (default 5s) and `hls_playlist_length`. To get below 5s, consider LL-HLS with `hls_fragment 1s` and a compatible player (hls.js 1.x), or switch to WebRTC if you need sub-second latency.

Can I stream from OBS directly to HLS without RTMP?

Not natively. OBS exports in RTMP, SRT or WHIP (WebRTC). HLS is a distribution format for players, not an ingestion format. The standard flow is OBS → RTMP → Nginx-RTMP generates HLS → player consumes HLS over HTTP.

How do I restrict who can publish to the stream without exposing the key?

Use `on_publish` pointing to an HTTP endpoint of yours that validates the `name` (stream key) against a database. Nginx-RTMP only allows the publication if the endpoint returns 2xx. Combine it with a firewall closing port 1935 to authorized IPs only if the publisher base is fixed.

How many concurrent streams can an average server handle?

Depends on CPU and bandwidth. On a 4 vCPU + 8 GB VPS without transcoding (just passing RTMP through and generating HLS via direct copy fragments), you can serve 50-100 concurrent publishers. With H.264 transcoding via ffmpeg for multiple bitrates, it drops to 4-8 streams per CPU.

HLS fragments are being generated but the player cannot play — what should I check?

Three common causes: 1) CORS — add `add_header 'Access-Control-Allow-Origin' '*'` to the HLS location; 2) MIME type — confirm that `.m3u8` is `application/vnd.apple.mpegurl` and `.ts` is `video/mp2t`; 3) directory permission — `/var/www/hls` must be writable by the Nginx user (usually www-data).

Topics:
Next steps Infrastructure optimized for live streaming, recording and VOD.Set up your streaming server on Hostini →
Was this tutorial helpful?
Chat on WhatsApp