Desktop and media video streaming server with OBS Studio, nginx, RTMP, HLS and video.js


Due to current circumstances, video streaming and desktop sharing has found an enormous increase in usage. This post contains a quick guide to set up an independent video streaming server with nginx and RTMP, which OBS Studio clients can stream to. Viewers can connect via a website and receive the stream in their browser, using HLS and the video.js HTML5 framework. OBS is so unbelievably easy to use, it's fantastic for remote tutorials and application/game streaming where you wish to use multiple media inputs (microphone, desktop, webcam) and have good control of the output composition.

The following guide is based on Debian 10 (Buster), nginx 1.14.2 and OBS Studio 25.

Step 1: Install and set up nginx with RTMP module

Install the nginx package and the RTMP module with apt install nginx-full libnginx-mod-rtmp. Configure the nginx.conf http section similar to the one shown below, e.g. enable gzip for certain file types and add/delete entries from the available TLS versions. On the top level, add the rtmp section. See the nginx-rtmp-module directives reference for more information on the individual keywords.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
http {
    sendfile on;  # use sendfile for static files (.ts, .m3u8)
    tcp_nopush on;  # used in combination with sendfile
    tcp_nodelay on;  # decrease latency for small packets

    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    ssl_protocols TLSv1.2 TLSv1.3;  # remove old protocols, add TLS 1.3
    ssl_prefer_server_ciphers on;

    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;

    gzip on;  # use gzip for file types defined below
    gzip_comp_level 9;
    gzip_http_version 1.1;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}

rtmp {
    server {
        listen 1935;  # Standard RTMP port

        application live {
            live on;
            hls on;
            hls_path /var/www/hls;  # where fragments are stored
            hls_fragment 3;  # length of each segment in seconds
            hls_playlist_length 30;   # length of total recording in seconds
            hls_cleanup on;  # delete fragments on restart/shutdown
            deny play all;  # disable RTMP viewer clients (not streaming clients)
        }
    }
}

Then create the directory /var/www/hls and let www-data own it. With this configuration, nginx now receives incoming RTMP streams on port 1935 (rtmp://SERVER_NAME/live) and stores each stream as HLS fragments. Note that the module does not support RTMPS (RTMP over TLS), so everything is sent unencrypted (source: Debian module repository).

Next, set up a virtual host in /etc/nginx/sites-enabled, for example use the default vhost that exists already. In it, the following server content is needed. Be aware this server is configured to use HTTPS with a certificate. You can obtain automatically issued free certificates with certbot (package certbot) for your hostname, but that's out of scope for now.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;

    server_name SERVER_NAME;
    ssl_certificate ...;
    ssl_certificate_key ...;

    root /var/www/html;
    index index.html;

    location /hls {
        add_header Cache-Control no-cache;  # disable client-side caching

        # Source: https://docs.peer5.com/guides/setting-up-hls-live-streaming-server-using-nginx/
        add_header 'Access-Control-Allow-Origin' '*' always;
        add_header 'Access-Control-Expose-Headers' 'Content-Length';
        if ($request_method = 'OPTIONS') {  # allow CORS preflight requests
            add_header 'Access-Control-Allow-Origin' '*';
            add_header 'Access-Control-Max-Age' 1728000;
            add_header 'Content-Type' 'text/plain charset=UTF-8';
            add_header 'Content-Length' 0;
            return 204;
        }

        types {
            application/vnd.apple.mpegurl m3u8;
            video/mp2t ts;
        }

        root /var/www;
    }
}

The HLS fragments are now available at https://SERVER_NAME/hls. CORS configuration is needed if you embed this resource from other domains. Note that alias could be used as well, but root is prefered in this case.

Step 2: Add HTML streaming client

If you finished step one, nginx is now ready to receive RTMP streams and to distribute these packets via HLS. Next, we will add an HTML page which offers a video client for viewers.

The page is pretty simple, just save it as index.html in /var/www/html/. You also need the video.js libraries. video.min.js and video-js.min.css you can get from the video.js Github releases page, videojs-http-streaming.min.js is available at unpkg.com/@videojs.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
<html>
    <head>
        <title>streaming client</title>
        <meta charset="utf-8"/>
        <link href="video-js.min.css" rel="stylesheet" />
    </head>
    <body>
        <center>
        <h1>live streaming</h1>
        <video-js id="liveplayer" controls autoplay width=600 class="vjs-default-skin">
            <source src="https://SERVER_NAME/hls/STREAM_KEY.m3u8" type="application/x-mpegURL">
        </video-js>
        </center>
    </body>

    <script src="video.min.js"></script>
    <script src="videojs-http-streaming.min.js"></script>
    <script>
        var player = videojs("liveplayer");
        player.play();
    </script>
</html>

Replace the SERVER_NAME placeholder in line 11 with the hostname your nginx is reachable at (server_name configuration entry). The STREAM_KEY URI part must also be replaced, the value being the name of the stream you wish to view in this video element. We will come back to this value in part three.

Step 3: Configure OBS

Now it is time to connect the streaming client. OBS supports a wide set of video streaming services like Twitch or YouTube Gaming out of the box, but we will use our own!

Start by going into the settings menu. In the stream settings dialog, use custom from the drop down service list. Two empty input fields will appear. In the server field enter rtmp://SERVER_NAME/live, the second field stream key being the name of the stream. For example, if you use mylivestream as the key, the video source URL created by nginx (used in the HTML code above) would be https://SERVER_NAME/hls/mylivestream.m3u8.

Don't forget to also configure your audio and video settings. I reached good performance with a video bitrate of 90% of my total upload speed, setting the resolution to native full HD 1920x1080 (no downscaling) and 30 FPS. If available, enable hardware-based video encoding (e.g. NVIDIA NVENC).

As soon as you hit the "Start Streaming" button, OBS connects to the RTMP endpoint, sends its packets and nginx transforms them into HLS fragments. A viewer can then visit the URL where the index.html from step two is delivered at and receives a web page with the video.js video player!

During recording/streaming the stats window allows you to view statistics about upload size and dropped frames. You might also notice that there is a delay of around 20 to 30 seconds between sending and receiving, which might be acceptable in most scenarios, but may interfere with your workflow if viewers have a possibility to comment your content live during the streaming session. There are ways to reduce this delay, with the cost of increasing traffic (more smaller packets mean more HTTP requests by clients). In a test I could reduce the time difference between sending and receiving to six seconds (key frame every two frames in OBS, HSL fragment size of 2 and list length 10). If you need realtime bidirectional conferencing, jitsi might be a better alternative.

Background

For reference, here's a list of terms mentioned in this article:

  • RTMP: Real-Time Messaging Protocol, the sender's streaming protocol. Originally developed by Macromedia (Flash, now Adobe) and pretty old standard. Yet still used widely by many video streaming providers.
  • HLS: HTTP Live Streaming, by Apple. Takes an incoming stream (e.g. RTMP) and chunks it into fragments, creating downloadable files for clients. HLS clients understand the .m3u8 playlist with its .ts entries and continously request the next fragments, generating an uninterrupted output stream.
  • MPEG-DASH: Dynamic Adaptive Streaming over HTTP, similar to HLS, standardised by the MPEG.
  • M3U: MP3 URL, playlist format for continous updates to internet streams (players request one file and get a list of items in the playlist which they download). The .m3u8 files are used for HLS fragments, where nginx prepares the extended M3U entries in the playlist file, updating the playlist with each new fragment.
  • SRT: Secure Reliable Transport, developed by Haivision and backed by the SRT Alliance community. New transport standard published under an open source license, with encryption, packet loss detection and dynamically adapting transport as core features. Support in OBS is experimental, referencing the state of implementation in ffmpeg in their forums.

Thanks for reading and happy streaming!

By the way, the OBS client used in this setup is running inside my virtualised Windows machine with PCI passthrough. I have blogged about this in the past: "Using a PCI graphics card in KVM/QEMU on Debian Stretch". All peripheral stuff like a webcam or USB microphone for OBS can be passed as USB host devices as well.