Good evening

Today we are going to make our own video streaming server on a Linux machine. We will create it in a Docker container, so I assume you already have it installed.

Before I start, I’d like to say thanks to the author of this and this tutorials, it was very helpful.

So let’s dive right in.

Setting up the container

It is as simple as

docker run -it --name streaming4 -p 21935:1935 -p 21936:1936 -p 21937:80 ubuntu:18.04 bash

You may replace streaming4 with any name you want for the container. You may even omit --name streaming4, and Docker will generate a name for you.

We have opened up three ports here. You may replace them with whichever you like, but keep in mind that you may need to change them in the settings that follow. The ports serve the following purposes:

  • 21935 - the entry point for streaming. You will send your video stream here.
  • 21936 - the web interface will read the video stream on this port
  • 21937 - you, as a user, will connect here via your browser to access the web player

Installing nginx

Now we need to install nginx web server with the essential plugin for RTMP protocol support. Since we are in a Docker container, we don’t need sudo, we are already root user.

apt update

apt install -y nginx libnginx-mod-rtmp

We can run it now by simply running nginx to make sure that it has installed properly. It will out put nothing and that is okay.

Creating the stream directory

Now we need to create a directory that will be a root for stream connection, and set the ownership so nginx would have an access to it.

cd /mnt
mkdir hls
chown www-data:www-data hls

www-data is the user of nginx. In some versions it may be www, maybe something else. I simply ran top to see the user of nginx process to check it out.

Setting up nginx for streaming

Open /etc/nginx/nginx.conf with your preferred text editor. You will see a lot of default configuration blocks, ignore them for now.

RTMP streaming

Put the following block at the very end of the file.

rtmp {
        server {
                listen 1935;
                chunk_size 4096;

                application live {
                live on;
                record off;

                #allow publish;
                deny publish all;

                # Turn on HLS
                hls on;
                hls_path /mnt/hls/;
                hls_fragment 3;
                hls_playlist_length 60;
                # disable consuming the stream from nginx as rtmp
                deny play all;

1935 is the internal port for streaming input. Even though the streaming software on our computer will be connecting to 21935, it will be redirected to the internal 1935 port (as we have set when we created the container) and it is the one we need to set here.

At this point streaming will not work because we have blocked it with deny publish all;. To allow streaming from your computer, uncomment the allow publish line and replace with your external IP address.

Connection point for the player

Now we need to create an point for our web player to connect to. It has a variety of necessary options.

Now, attention, this block should go at the end of the http block, which most likely exists by default in the config. Not at the end of the whole file!

server {
        listen 1936;

        location / {
            # Disable cache
            add_header 'Cache-Control' 'no-cache';

            # CORS setup
            add_header 'Access-Control-Allow-Origin' '*' always;
            add_header 'Access-Control-Expose-Headers' 'Content-Length';

            # allow CORS preflight requests
            if ($request_method = 'OPTIONS') {
                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/dash+xml mpd;
                application/ m3u8;
                video/mp2t ts;

            root /mnt/;

Again the port 1936 is the internal port we have set for these services. Our web player will be connecting to 21936 though.

Finishing nginx configuration

Now save the file and exit the editor. Run the following to apply the settings.

nginx -s reload

If nothing is printed, then everything is okay.

Let’s try streaming

At this point you can actually start streaming video. I’ll show you how to do it using OBS Studio. I assume that you already have it installed and know how to actually stream (setting up the scene, video quality settings, etc.). I’ll just show the configuration you need to connect to your newly made server.

In File menu choose Settings. Go to Stream tab. All relevant configuration will be done here.

  • Select “Custom” from Service dropdown menu.
  • In Server, set rtmp://; replace with the IP address of your server. Notice that we are using the external port we have set for streaming on the Docker container.
  • In Stream Key, you can write pretty much anything. Just remember what it is, we’ll need it soon.

Click OK, and now you may try streaming by clicking the Start Streaming button. You will see a green square and a non-zero streaming speed at the bottom-right of the window, if it works fine.

The web player

But now we want to check whether it actually plays or not. Create a file called vid.html (or whichever name you prefer, just remember it) with the following contents


  <link href="" rel="stylesheet" />

  <!-- If you'd like to support IE8 (for Video.js versions prior to v7) -->
  <script src=""></script>


body {
margin-top: 0px;
margin-left: 0px;
margin-bottom: 0px;
margin-right: 0px;



    class="video-js vjs-fill"
    <source src="" type="application/x-mpegURL" />
    <p class="vjs-no-js">
      To view this video please enable JavaScript, and consider upgrading to a
      web browser that
      <a href="" target="_blank"
        >supports HTML5 video</a

  <script src=""></script>


Replace with the IP address of your server, and MYSTREAMKEY with the stream key you have set in OBS settings. Notice again, we are using the external port 21936 for our player’s connection.

This creates a video player that occupies the whole screen.

Now you can simply open this file locally in your web browser, and it should play your stream. Success so far!

Setting up the player on the server

But we are not going to give out this file manually to every interested person, now are we? How about we put it on our server, so a user could access it by simply going to an address in their web browser.

Let’s go back to our container. Navigate to /var/www/html, delete the default files in there and put the vid.html file there. I cheated, I simply opened text editor, copypasted the contents and saved it; didn’t feel like copying it to the server and then docker cp-ing or whatever.

Now we need to edit /etc/nginx/sites-enabled/default (the actual file is located in sites-available, and sites-enabled must contain the symlink to it). Somewhere within the server block should be a line starting with index. It contains the default index files which we have just deleted. Replace this line with index vid.html;. Also make sure that root /var/www/html; is present, since it is the directory where we have put our file. Save and don’t forget to nginx -s reload.

I myself encountered a strange error here, so I’ll describe it so you could fix it if it happens to you. When I tried to nginx -s reload, it showed nginx: [emerg] a duplicate default server for in /etc/nginx/sites-enabled/default~:22. The workaround is to change the 80 in listen 80 default_server; and listen [::]:80 default_server; to something else, for example 81; then saving, then nginx -s reload, then going back into the file, changing back to 80, saving and nginx -s reload. It probably conflicts with itself at some point or something.

Finally, go to your web browser enter, where (you’re probably tired of this already) is the IP address of your server and 21937 is the external port we have opened up in the beginning. And it should show your player with your stream. We’re done here.

In conclusion

We have created a video streaming server, and we didn’t have to contaminate the native settings on our server. Moreover, we can docker commit the container for safekeeping or so we could transfer all the work we’ve done here to another server. Quite convenient.

I haven’t touched upon encryption or any kinds of authorization for viewers. I haven’t figured it out myself yet. Bending my mind over this task was a challenge already.

Thank you for dropping by!