Build a live-stream server

!! THIS GUIDE IS OUT-DATED. YOU MAY WANT TO HAVE A LOOK AT A MORE RECENT GUIDE I MADE HERE: https://www.ustoopia.nl/featured/create-a-secure-adaptive-bit-rate-hls-stream-with-nginx-rtmp-ffmpeg-on-ubuntu-20-04-2021/

This guide will show you step by step how to install Nginx webserver & Certbot SSL certificates, the RTMP module, Ffmpeg on a Ubuntu 18.04 VPS server. (WILL ALSO WORK ON UBUNTU 20.4). It will then will also explain how to setup HLS live-streaming and create a webpage with Video.JS to show the live-stream.
I decided to write this guide after receiving a lot of questions and responses on a video or two that I made a while ago. HLS and SSL related mostly. So hopefully this guide will clear up all of those questions for you all. If not, please leave any questions at the bottom of this page or as comment on the Youtube page.

This guide contains 7 steps. I strongly advice to read through them all beforehand so you’ll know what you’re doing in stead of just blindly start copy/pasting everything, because that rarely works out like it should, in my experience. This looks like a long-ass guide, and it is in a way, but you could do this all in under 25 minutes. Anyways, it’s too long for this blog’s layout so click on the continue reading link below to read the full article and to see the video.

EDIT 24-07-2020 – Take a look at the git repo for easy hls site instal by Quinn Ebert. He has created it based on my guide and video. Thanks Quinn! Good work πŸ™‚

YouTube player

TIP! Please use a fresh install for Ubuntu 18.04 so you won’t run in to any conflicts or other issues.

Let’s get started! So you’ve installed your server or VPS with Ubuntu 18.04, and you’re now logged in to the local console or through SSH from a remote location. Let’s start of by making sure the system is up to date.

sudo apt update && sudo apt upgrade

1: Install Nginx + RTMP module.

sudo apt install -y nginx
sudo apt install -y libnginx-mod-rtmp

When you enter the IP address of your server in your web-browser you should now see a default Nginx webpage.

2: Installing required & additional software.

Add Certbot repository to our server so it will install the version we want later on.

sudo apt install -y software-properties-common
sudo add-apt-repository ppa:certbot/certbot

Add architecture for i386 because we will need it later on when we’re going to install additional codecs.

sudo dpkg --add-architecture i386
sudo apt update

We’re ready to start installing a bunch of additional, required software, and please be aware that it might take several moments to complete.

sudo apt install wget nano python-certbot-nginx ufw unzip software-properties-common dpkg-dev git make gcc automake build-essential joe ntpdate zlib1g-dev libpcre3 libpcre3-dev libssl-dev libxslt1-dev libxml2-dev libgd-dev libgeoip-dev libgoogle-perftools-dev libperl-dev pkg-config autotools-dev gpac ffmpeg sysstat nasm yasm mediainfo mencoder lame libvorbisenc2 libvorbisfile3 libx264-dev libvo-aacenc-dev libmp3lame-dev libopus-dev libfdk-aac-dev libavcodec-dev libavformat-dev libavutil-dev g++ libc6:i386 freeglut3-dev libx11-dev libxmu-dev libxi-dev libglu1-mesa libglu1-mesa-dev

The following two steps are totally optional!! It includes PHP, MySQL or MariaDB, PhpMyAdmin. Please refer to my Youtube video to see more information on this.

sudo apt install mariadb-server mariadb-client phpmyadmin php php-cgi php-common php-pear php-mbstring php-fpm

3: Setup a firewall and perform other required steps.

Next we’ll be performing various required steps and setup our UFW firewall. We’re going to start with downloading the RTMP module source files because we need a certain file from it that we need to copy to /var/www/html.

cd /usr/src
git clone https://github.com/arut/nginx-rtmp-module
cp /usr/src/nginx-rtmp-module/stat.xsl /var/www/html/stat.xsl

Create the file ‘crossdomain.xml’ in /var/www/html folder, and paste in it what you see below.

sudo nano /var/www/html/crossdomain.xml
<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd">
<cross-domain-policy>
<allow-access-from domain="*"/>
</cross-domain-policy>

Create the file ‘info.php’ in /var/www/html folder, and paste in it what you see below.

sudo nano /var/www/html/info.php
<?php
phpinfo();
?>

If you want to display an image whenever people open your web page while you are not streaming, make sure it’s a jpg file with resolution 1280×720 or 1920×1080, placed in /var/www/html and named “poster.jpg”
For testing purposes you can always use a random publicly available poster from the web like the one I used for this guide:

wget -O /var/www/html/poster.jpg https://i.imgur.com/gTeWLDO.jpg

Create these folders. You can choose a different location but remember to also change the locations in the Nginx config files.

sudo mkdir /var/livestream
sudo mkdir /var/livestream/hls
sudo chown -R www-data: /var/livestream

Now it’s about time to install our firewall. UFW is commonly used and should already be installed. If this is not the case do “sudo apt install ufw” first.

ufw status
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 10000/tcp
sudo ufw allow 1935

Port 1935 is the one that RTMP uses so we need to open that on the firewall and perhaps in some cases you will also need to forward the port on your router.
To make things easy for yourself if you are setting up your server from a remote location is to add the external IP address of your remote location to the allow list. Before enabling the firewall make VERY SURE that you’ve allowed yourself access to the server on your SSH port or else you’re going to lock yourself out of the server.

sudo ufw allow from XX.XX.XX.XX
sudo ufw enable
sudo ufw status verbose

4: Edit Nginx configuration.

sudo nano /etc/nginx/nginx.conf

On line 2 change the worker_processes option from auto to 1, so it says: worker_processes 1;

Scroll all the way down and add the following at the end of the file, or something similar if you’re situation requires other variables (use your brain πŸ™‚

rtmp {
        server {
                listen 1935;
                chunk_size 8192;
	application live {
		live on;
		interleave off;
		meta on;
		wait_key on;
		wait_video on;
		idle_streams off;
		sync 300ms;
		session_relay on;
		allow publish all;
		allow play all;
		max_connections 1000;
		## == FORWARD STREAM (OPTIONAL) == ##
		# == == TWITCH RE-STREAM == == #
		# push rtmp://live-ams.twitch.tv/app/LIVESTREAM_KEY;
		# == == YOUTUBE RE-STREAM == == #
		# push rtmp://a.rtmp.youtube.com/live2/LIVESTREAM_KEY;
		# == == MIXER.com RE-STREAM == == #
		# push rtmp://ingest-ams.mixer.com:1935/beam/LIVESTREAM_KEY;
		publish_notify off;
		# play_restart off;
		# on_publish http://your-website/on_publish.php;
		# on_play http://your-website/on_play.php;
		# on_record_done http://your-website/on_record_done.php;
			
		## == HLS == ##
		hls off;
		# hls_nested on;
		# hls_path /var/livestream/hls/live;
		# hls_base_url http://;
		# hls_playlist_length 60s;
		# hls_fragment 10s;
		# hls_sync 100ms;
		# hls_cleanup on;
		## == DASH == ##
		dash off;
		# dash_nested on;
		# dash_path /var/livestream/dash;
		# dash_fragment 10s;
		# dash_playlist_length 60s;
		# dash_cleanup on;
		push rtmp://localhost/hls;
		}
		
	application hls {
		live on;
		allow play all;
		hls on;
		hls_type live;
		hls_nested on;
		hls_path /var/livestream/hls;
		hls_cleanup on;
		hls_sync 100ms;
		hls_fragment 10s;
		hls_playlist_length 60s;
		hls_fragment_naming system;
		}
	}
}
nginx -t
sudo systemctl restart nginx
sudo nano /etc/nginx/sites-available/default
server {
	listen 80 default_server;
	listen [::]:80 default_server;
	# listen 443 ssl http2 default_server;
	# listen [::]:443 ssl default_server;
	# include snippets/snakeoil.conf;
	keepalive_timeout 70;
	gzip off;
	root /var/www/html;
	# Add index.php to the list if you are using PHP
	index index.php index.nginx-debian.html index.html index.htm;
	server_name _;
	# add_header Strict-Transport-Security "max-age=63072000;";
	# add_header X-Frame-Options "DENY";
	location / {
		location ~* \.m3u8$ {
		add_header Cache-Control no-cache;
		}
		add_header Access-Control-Allow-Origin *;
		# First attempt to serve request as file, then as directory, then fall back to displaying a 404.
		try_files $uri $uri/ =404;
	}
	location ~ \.php$ {
		include snippets/fastcgi-php.conf;
	#	# With php-fpm (or other unix sockets):
		fastcgi_pass unix:/var/run/php/php7.2-fpm.sock;
	#	# With php-cgi (or other tcp sockets):
	#	fastcgi_pass 127.0.0.1:9000;
	}
	## deny access to .htaccess files, if Apache's document root concurs with nginx's one
	#location ~ /\.ht {
	#	deny all;
	#}
## This provides RTMP statistics in XML at 
location /stat {
	rtmp_stat all;
	rtmp_stat_stylesheet stat.xsl;
	# auth_basic "Restricted Content";
        # auth_basic_user_file /etc/nginx/.htpasswd;
	}
## XML stylesheet to view RTMP stats. Copy stat.xsl wherever you want and put the full directory path here
location /stat.xsl {
	root /var/www/html/;
	}
}
nginx -t
sudo systemctl restart nginx

Obviously you need to change the DOMAIN part in the next lines to whatever your domain name is.

sudo nano /etc/nginx/sites-available/DOMAIN.net.conf

Add the following to this new file, but don’t forget to change DOMAIN first!

server {
    listen 80;
    listen [::]:80;
    root /var/www/html;
    server_name DOMAIN.net www.DOMAIN.net;
}
nginx -t

If everything is good and we don’t see any error messages we can create the symlink to the config file so it will be activated for Nginx. Use your brain and edit the DOMAIN parts.

ln -s /etc/nginx/sites-available/DOMAIN.net.conf /etc/nginx/sites-enabled/DOMAIN.net.conf
nginx -t
sudo systemctl restart nginx

5: Confirm that the RTMP stream works.

At this point you could try to test if you can livestream to your server using OBS or any other live-stream app. In your config use something similar as shown below.

You can check the status bar in OBS to check if you are able to connect and stream to the server. When it shows something similar to this, you’re good.

Our stat page at should now show one live RTMP source. Now you should already be able to watch the livestream using VLC Player or PodPlayer or any other media player that can play media from URL’s. Open it in your player like this: rtmp://DOMAIN.net/live/stream

It might take a couple of seconds but it should start loading the livestream video. If all works fine, we can continue to the next chapter. You’re doing great so far by the way! πŸ™‚

6: Create SSL certificates for Nginx

This first step is not required, but I advise you to go through it anyway because if you want to get tripple A++ rated certificates for your website, this will be required (see www.ssllabs.com/ssltest for info). The next command might take five minutes and probably even more (depending on your processing power ofc).

sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 4096

Now for the creation of the actual certificates for your domain. Use brain here!

sudo certbot --nginx -d DOMAIN.net -d www.DOMAIN.net

You will be asked to enter your e-mail address and two or three other questions. Don’t choose to enable the auto forward for http to https. You can always set this up later.
If everything went fine, you should see the location of the newly created certificates. Keep these in mind. Don’t forget to add a crontab so the certificates get renewed automatically in time.

sudo crontab -e
0 12 * * * /usr/bin/certbot renew --quiet

We’re going to edit the /etc/nginx/sites-available/DOMAIN.net.conf file that we’ve created earlier on.

sudo nano /etc/nginx/sites-available/DOMAIN.net.conf

Remove everything in this file, and afterwards paste the following:

server {
	listen 80;
	listen [::]:80;
	server_name YOURDOMAIN.COM;
	# redirect all http to https
        return 301 https://$server_name$request_uri;
}
server {
	listen 443 ssl http2;
	listen [::]:443 ssl;
        server_name YOURDOMAIN.COM;
	# include snippets/snakeoil.conf;
	keepalive_timeout 70;
	gzip off;
	root /var/www/html;
	# Add index.php to the list if you are using PHP
	index index.php index.nginx-debian.html index.html index.htm;
	ssl_certificate /etc/letsencrypt/live/DOMAIN.COM/fullchain.pem;
	ssl_certificate_key /etc/letsencrypt/live/DOMAIN.COM/privkey.pem;
	ssl_trusted_certificate /etc/letsencrypt/live/DOMAIN.COM/chain.pem;
	ssl_dhparam /etc/ssl/certs/dhparam.pem;
	ssl_protocols TLSv1.2 TLSv1.3;
	ssl_session_cache shared:le_nginx_SSL:1m;
	ssl_session_timeout 1440m;
	ssl_prefer_server_ciphers on;
	ssl_session_tickets off;
	ssl_stapling off;
	ssl_stapling_verify on;
	resolver 8.8.8.8 8.8.4.4 valid=300s;
	resolver_timeout 5s;
	ssl_ecdh_curve secp384r1;
	ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:ECDHE-RSA-AES128-GCM-SHA256:AES256+EECDH:DHE-RSA-AES128-GCM-SHA256:AES256+EDH:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4";
	add_header Strict-Transport-Security "max-age=63072000;";
	add_header X-Frame-Options "DENY";
	location / {
		location ~* \.m3u8$ {
		add_header Cache-Control no-cache;
		}
		add_header Access-Control-Allow-Origin *;
	# First attempt to serve file, then as directory, then a 404.
		try_files $uri $uri/ =404;
	}
	# pass PHP scripts to FastCGI server
	location ~ \.php$ {
		include snippets/fastcgi-php.conf;
	#	# With php-fpm (or other unix sockets):
		fastcgi_pass unix:/var/run/php/php7.2-fpm.sock;
	#	# With php-cgi (or other tcp sockets):
	#	fastcgi_pass 127.0.0.1:9000;
	}
	# deny access to .htaccess files, if Apache's document root concurs with nginx's one
	
	#location ~ /\.ht {
	#	deny all;
	#}
# This provides RTMP statistics in XML at http://your-server-address/stat
location /stat {
	rtmp_stat all;
	rtmp_stat_stylesheet stat.xsl;
	# auth_basic "Restricted Content";
        # auth_basic_user_file /etc/nginx/.htpasswd;
	}
# XML stylesheet to view RTMP stats. Copy stat.xsl wherever you want and put the full directory path here
location /stat.xsl {
	root /var/www/html/;
	}
# Control interface (extremely useful, but can also boot people from streams so we put basic auth in front of it - see https://github.com/arut/nginx-rtmp-module/wiki/Control-module for more info
#location /control {
	# you'll need a htpasswd auth file, that's outside the scope of this doc but any apache one will work
	# auth_basic "Restricted Content";
	# auth_basic_user_file /etc/nginx/.htpasswd;
	#rtmp_control all;
	#}
#creates the http-location for our full-res desktop HLS stream "http://my-ip/live/my-stream-key/index.m3u8"
location /live {
	# root /var/livestream/hls;
	alias /var/livestream/hls;
	expires -1;
	autoindex on;
	autoindex_localtime on;
	# CORS setup #
		set $sent_http_accept_ranges bytes;
		add_header 'Cache-Control' 'no-cache';
		add_header Cache-Control no-cache;
		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/vnd.apple.mpegurl m3u8;
		application/dash+xml mpd;
		video/mp2t ts;
		}
	}
}
nginx -t
sudo systemctl restart nginx

6: Video.js installation & and example index.html

Video.js can do a lot of extra things that I won’t go in to right now, so you should investigate yourself at https://github.com/videojs.
Create a new sub-folder in /var/www/html called videojs and enter this folder.

sudo mkdir /var/www/html/videojs
cd /var/www/html/videojs
wget https://github.com/videojs/video.js/releases/download/v7.7.6/video-js-7.7.6.zip

wget https://github.com/videojs/http-streaming/releases/download/v1.13.1/videojs-http-streaming.js

Optional! wget https://github.com/videojs/videojs-contrib-dash/releases/download/v2.11.0/videojs-dash.js

unzip /var/www/html/videojs/video-js-7.7.6.zip
chown -R www-data: /var/www/html
ls -la /var/www/html/videojs

The previous command should show something like what is shown below.

Now we’re going to create a file that will show the video.js player. You can name it whatever you want but here I’ll be naming it “index.html”

sudo nano /var/www/html/index.html
<!DOCTYPE html>
<html>
<head>
<script src='https://DOMAIN.net/videojs/video.js'></script>
<script src="https://DOMAIN.net/videojs/videojs-http-streaming.js"></script>
<meta charset=utf-8 />
<title>LiveStream</title>
<link href="https://DOMAIN.net/videojs/video-js.min.css" rel="stylesheet">
<!-- <link href="https://DOMAIN.net/videojs/videojs-sublime-skin.min.css" rel="stylesheet"> -->
<!-- <link href="https://DOMAIN.net/videojs/videojs-sublime-skin.css" rel="stylesheet"> -->
<!-- <link href="https://DOMAIN.net/videojs/video-js.css" rel="stylesheet"> -->
<!-- <link href="https://DOMAIN.net/videojs/videojs-skin-twitchy.css" rel="stylesheet" type="text/css">  -->
</head>
<body>
<center>
<video-js id="live_stream" class="video-js vjs-fluid vjs-default-skin vjs-big-play-centered" controls preload="auto" autoplay="true" width="auto" height="auto" poster="https://DOMAIN.net/poster.jpg">
<source src="https://DOMAIN.net/live/stream/index.m3u8" 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='https://videojs.com/html5-video-support/' target='_blank'>supports HTML5 video</a>
    </p>
</video-js>
  
  <script>
    var player = videojs('live_stream');
	player.play();
  </script>
</center>
</body>
</html>
chown -R www-data: /var/www/html

We’re basically done! Now it’s time to see if it all works. Streaming to your server and open your website or the file we’ve just created at: https://DOMAIN.net/index.html

Thanks for following my guide. Please report any issues and you can leave questions or other remarks on this page or the Youtube page of the video.

I posted all the commands in a pastebin right here. https://pastebin.com/qMCpKYC8