Create a secure adaptive bit-rate HLS stream with Nginx/RTMP/Ffmpeg on Ubuntu 20.04 (2021)

I wrote a new guide on setting up your own live-stream server. Mostly due to your requests. I took the opportunity to simplify the guide a bit. If you follow this guide step by step, and use your brain at times that it’s required, you’ll be fine. This guide has been tested on three occasions and it works 100%.

Before we begin I want to make something clear. Creating your own live-stream server like this can not be compared by using a streaming platform like Youtube or Twitch. They use a worldwide network to deliver their content, and we’re just using 1 server. That means that it matters where the server is located. If you host a server in Japan, it may not be as fast as you hoped in Ireland. Also keep in mind that when you decide to host your server from your home, that you bandwidth is limited. This can seriously affect your upload speed, especially when you have more then a handful of viewers. To allow more viewers you should host it in a VPS or dedicated server preferably that is in a datacenter with a >500mbps connection. The downside of that is that it might no be powerful enough to do all the transcoding ánd deliver the content to viewers. In other words, this setup is not suited to host a live-stream server to completely replace Youtube or whatever platform. So what is it good for then? For me personally I just use it to re-stream. So I don’t need the transcoding of my stream since I can just forward the RTMP stream to Youtube & Twitch. Also this setup will be fine when you’re using it below 100 viewers, and if the hardware is not from the year 0. And, it can also be used as a primary hub that you stream to and from there it gets distributed to several other servers located at different parts of the world. This can be VPS servers that you rent, or an already existing CDN. That last thing is actually exactly what I’ve been fooling around with, just for fun. Anyways, hopefully all this meets your requirements.


In this guide we’ll be setting up a live-stream server on Ubuntu 20.04 powered by Nginx+RTMP mod, and we’ll be using FFmpeg to transcode incoming RTMP streams to several outgoing HLS, adaptive bit-rate streams, or ABS in short, that will allow the most optimal, smooth playback on any type device/screen, even on slow, low-bandwidth internet connections.


I’ll assume that you have already done a clean basic install of a server running at least Ubuntu 20.04. You can choose to run the server on an older, spare PC, or a Virtual Machine, or a rented VPS, or a dedicated server perhaps even, or… and I personally did this often times, use a laptop that you don’t really use anymore, one that has a h264 Nvenc capable Nvidia GPU chip in it gives you the option the do all the encoding on your server with the GPU, freeing up a lot of CPU power for other tasks. More on that in a future article. At least a Core i5 is advised, with either 4GB of RAM, but preferably 8GB, or more. More is always better! (One can never have too much RAM).

It is essential that you open or forward ports 80 and 443 in your firewall/router and point it to your server. If you want to allow others to stream to your server from the internet port 1935/udp for RTMP must also be pointing to your server. This is the very first thing you must do before you do anything else. Instructions on how to do this are beyond the scope of this guide. On this site you can test if you have successfully opened the specified ports:

0. Preparing the server

We’re going to set a proper host name for the server first, ideally a FQDN, as this will be a requirement to successfully set up certificates for HTTPS later on. You can get a dynamic DNS domain name for free at to name just one. In this how-to guide I’ll be using a dummy example addresses like: “yourdomain” or “”. Now please make sure that from this point on you will replace every line that contains this address with the actual address of your server. I’m not going to repeat this so please keep this in mind and pay attention to it.

We’re gonna start by installing Nginx and the RTMP module for Nginx, and Certbot. We ‘re also installing a whole bunch of things that we will be needing or using later on. Let’s first get the server up to date.

sudo apt update && sudo apt upgrade -y

1. Installing Nginx + RTMP module and a bunch other stuff

sudo apt-get install wget unzip software-properties-common dpkg-dev git make gcc automake build-essential 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 mediainfo mencoder lame libvorbisenc2 libvorbisfile3 libx264-dev libvo-aacenc-dev libmp3lame-dev libopus-dev -y

Time to install Nginx and the RTMP module and Certbot. Be sure you don’t have anything else like Apache running on port 80 as it will conflict resulting in that Nginx will not start.

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

2. Manually create these folders and files

We now have the libnginx-mod-rtmp module installed, but it doesn’t include a specific file that is needed to show a statistics web-page for you live-streams. Cause stats are cool of course, but more importantly, it is an essential tool to see if your streams are working. In case you ever need to troubleshoot an issue with a stream this is a handy tool. We’re going to obtain it from the official libnginx-mod-rtmp github repository. But first, we need to create the folder that we are going to copy this file to. This happens to be the same folder as we’ll be using as root folder for our video web-player files. And perhaps also a WordPress site later on. Oh yeah, you’re going to need a domain name that is pointing to the IP address of your server in DNS. Create these folders but be sure to replace “yourdomain” with whatever domain name you’ll be using.

sudo mkdir -p /var/www/yourdomain/web/js/videojs

sudo mkdir -p /var/livestream/hls /var/livestream/dash /var/livestream/recordings /var/livestream/keys

sudo ln -s /var/livestream/hls /var/www/yourdomain/web/hls
sudo ln -s /var/livestream/dash /var/www/yourdomain/web/dash

sudo chown -R www-data:www-data /var/livestream /var/www/yourdomain
cd /usr/src
sudo git clone

Now let’s copy the stat.xsl file to your website’s root folder, and also to the default web folder .

sudo cp /usr/src/nginx-rtmp-module/stat.xsl /var/www/html/stat.xsl
sudo cp /usr/src/nginx-rtmp-module/stat.xsl /var/www/yourdomain/web/stat.xsl

Now we’re going to create a new file in he same folder and paste the lines below and save the file.

sudo nano /var/www/html/crossdomain.xml
<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy SYSTEM "">
<allow-access-from domain="*"/>
sudo cp /var/www/html/crossdomain.xml /var/www/yourdomain/web/crossdomain.xml

The following will create a temporary index.html file in your root web folder that is needed for setting up certificates later on.

sudo cp /var/www/html/index.nginx-debian.html /var/www/yourdomain/web/index.html

We need to tell Nginx that it has to use a different config file for handling all the requests that it will receive when using your domain name in a browser. For this we’re going to create a separate, new virtual host config file. Continue with the next step to do so.

3. Set a basic Nginx setup for certificate requests

In order to get valid certificates we need to use a hostname and run a website on it so that Letsencrypt can validate it. An IP address won’t do, so make sure you actually use a hostname in the next config file.

sudo nano /etc/nginx/sites-available/yourdomain.conf

Copy and paste the following in this new file, and pay attention to the code because you will need to replace a couple of lines so it shows your own domain name in stead of “yourdomain”.

server {
	listen 80;
	listen [::]:80;

	server_name YOUDOMAIN;				# << EDIT THIS! (Example:
	root /var/www/YOURDOMAIN/web;			# << EDIT THIS! (Example: /var/www/

	index index.html index-nginx.html index.htm index.php;
	client_max_body_size 8192M;
	add_header Strict-Transport-Security "max-age=63072000;";
	add_header X-Frame-Options "DENY";

	location / {
		add_header Cache-Control no-cache;
		add_header Access-Control-Allow-Origin *;
		try_files $uri $uri/ =404;
    location /crossdomain.xml {
		root /var/www/html;
		default_type text/xml;
		expires 24h;
	location /control {
		rtmp_control all;
		add_header Access-Control-Allow-Origin * always;
    location /stat {
		rtmp_stat all;
		rtmp_stat_stylesheet stat.xsl;
		#auth_basic Restricted Content;		# Create a valid .htpasswd before uncommenting this.
		#auth_basic_user_file .htpasswd;	# Create a valid .htpasswd before uncommenting this.
    location /stat.xsl {
		root /var/www/YOURDOMAIN/web;		# << EDIT THIS! (Example: /var/www/
	location ~ /\.ht {
		deny all;
    location /hls {
		types {
		application/ m3u8;  
		video/mp2t ts;  
		autoindex on;
		alias /var/livestream/hls;			# << Take note of this line. Change it when required.

		expires -1;
		add_header Strict-Transport-Security "max-age=63072000";
		add_header Cache-Control no-cache;
		add_header 'Access-Control-Allow-Origin' '*' always;
		add_header 'Access-Control-Expose-Headers' 'Content-Length';
		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;
        location /dash {
		application/dash+xml mpd;
		video/mp4 mp4;
		autoindex on;
		alias /var/livestream/dash;			# << Take note of this line. Change it when required.

		expires -1;
		add_header Strict-Transport-Security "max-age=63072000";
		add_header Cache-Control no-cache;
		add_header 'Access-Control-Allow-Origin' '*' always;
		add_header 'Access-Control-Expose-Headers' 'Content-Length';
		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;

To enable your new site you must create a virtual link from the sites-enabled folder to your config file like this:

sudo ln -s /etc/nginx/sites-available/YOURDOMAIN.conf /etc/nginx/sites-enabled/YOURDOMAIN.conf

You should always use the next command after making this many changes to Nginx’ configuration. It will check if your config is good, and if it contains errors it will show you what the error is.

sudo nginx -t

sudo systemctl restart nginx

4. Use Certbot to request valid certificate for your domain.

You should now be able to see a default Nginx web page when you enter the IP address of your server in your web browser if you’re on the same network. If you connect through the internet to a remote server keep in mind that you may need to open or forward port 80 in your firewall / router. Doing that is beyond the scope of this article. If you are behind a modem/router and you want your server to be accessible from the internet, you must also make sure you setup up port forwarding. Don’t continue this guide until you’ve done so because you will not be able to successfully create certificates if your server is not reachable from the internet on pot 80 (and later on also port 443). Once you’ve confirmed that your server is reachable from the internet and is showing the default Nginx page, it is now possible to create our certificates that allow us to secure the server by employing Https. We can do this with Cerbot very easily. You can enter multiple domains in one line if you need to. See example below.

sudo certbot --nginx -d YOURDOMAIN

Example: sudo certbot –nginx -d -d -d

A new request will be queued while it asks you some questions like your email address and if you agree to something. Entering email is required by the way. If all went well, you should see a message at the end asking you if you want to have Certbot automatically change some lines in your virtual host config file and whether or not to forward all Http traffic to Https from now on. Choose yes unless you have a good reason not to do so. When you open /etc/nginx/sites-enabled/youdomain.conf you will notice that several lines have been added to the bottom of the config file. It will look something similar to this:

listen [::]:443 ssl ipv6only=on; # managed by Certbot
listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/yourdomain/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/yourdomain/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

}server {
	if ($host = yourdomain) {
		return 301 https://$host$request_uri;
	} # managed by Certbot

	listen 80;
	listen [::]:80;

	server_name yourdomain;
	return 404; # managed by Certbot

This means that you can now open your domain in your browser using a valid certificate. You may want to check if this is actually the case simply by opening your domain name in a web browser. You should not see a warning about certificates and when you investigate the certificate you will see it’s valid.

5. Edit the Nginx.conf to enable RTMP and set related variables like ABS.

Now is a good time to edit the main Nginx config file to add our config for the RTMP server and Ffmpeg encoding. It is safe to replace the current config file with the one below. Making a backups of it never hurts.

sudo mv /etc/nginx/nginx.conf /etc/nginx/nginx-original.conf

Either create a new file and paste what is shown in the oversized codeblock below, and save it after having added all the required lines. Or simply place it where it belong by pulling it directly from my github page, like so:

sudo wget -O /etc/nginx/nginx.conf

I strongly suggest to open this config file in a text editor and maximizing the window so the layout is not messed up due to line-breaks or wrapped long lines as it is on this page. It will look neat, like the screenshot, and that makes it easier to read the descriptive and informative comments behind the variables that we put together.

This is what the config file looks like without line-breaks and wrapped long lines.

After creating the new nginx.conf file, open it in your favorite text editor or directly from the command line editor Nano / Joe, and go through it carefully. Almost all the variables have a description that says what the variable exactly does, and what the other options for it are. So make any adjustments if you feel you need to. It will work as-is, without making any changes, so if you made changes that don’t work or that you don’t like, just replace it again with this one.

sudo nano /etc/nginx/nginx.conf

Use the small icon in the top right of the lines below to copy the whole content so you can paste it in a decent text editor so it becomes more easy to go through it.

## ====================================================================================================== ##
## Visit this page for a list of all variables: ##
## Visit this site for many more configuration examples:	  ##
## This example file was put together by Andre "ustoopia" for usage on       ##
## ====================================================================================================== ##

user www-data;			# Only used on linux. Nginx will run under this username.
worker_processes 1;		# Set this to how many processors/cores CPU has. Relates to "worker_connections"
pid /run/;		# Sets the location of the process id file (used on linux only).
include /etc/nginx/modules-enabled/*.conf;	# Include all the optional configuration files stored here.

events {
	worker_connections 768;		# Worker_processes * worker_connections = max clients. So in this setup: 1 * 768 = 768 max clients.
	# multi_accept on;		# "Off" will accept 1 new connection at a time. "On" will accept all new connections. Default is off.

http {
	sendfile off;				# on|off. Toggles the use of sendfile. Default=off. For optimal HLS delivery disable this.
	tcp_nodelay on;				# on|off. Forces a socket to send the data in its buffer, whatever the packet size. Default=on.
	tcp_nopush on;				# on|off. Sends the response header and beginning of a file in one packet. Default=off.
	server_tokens off;			# on|off|build. Toggles showing nginx version in the response header field. Default=on.
	keepalive_timeout 65;			# A keep-alive client connection will stay open for .. on the server side. Use 0 to disable. Default=75s
	types_hash_max_size 2048;		# Sets the maximum size of the types hash tables. Default=1024.
	server_name_in_redirect off;		# Toggles the use of the primary server name, specified by the server_name directive. Default=off.
	server_names_hash_bucket_size 64;	# Sets the bucket size for the server names hash tables depending on processor's cache line, 32|64|128.
	default_type application/octet-stream;	# Emit this MIME type for all requests.
	## Include configuration files from these locations ##
	include /etc/nginx/mime.types;
	include /etc/nginx/conf.d/*.conf;
	include /etc/nginx/sites-enabled/*.conf;	# Holds symlinks to the actual config files in /etc/nginx/sites-available

	## LOGGING - This section has many options.	See ##
		access_log /var/log/nginx/access.log;		# off|path [format_name]. Default logging is done to same file as HTTP logger.
		error_log /var/log/nginx/error.log warn;	# Set this here or in the virtual hosts config file in sites-available folder.
	gzip off;		# on|off. Compresses responses using gzip method. Helps to reduce size of transmitted data by half or more. Default=off
	# gzip_vary on;		# More info on zip variables is found here:
	gzip_proxied any;
	# gzip_comp_level 6;
	# gzip_buffers 16 8k;
	gzip_http_version 1.1;
	gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;


rtmp {
	server {
	listen 1935;	# RTMP listen port. Open it in your router/firewall. Options: (addr[:port]|port|unix:path) [bind] [ipv6only=on|off] [so_keepalive=on|off|keepidle:keepintvl:keepcnt|proxy_protocol]

	application live {	# Name it whatever you prefer. You will need at least one application, you can have many more.
	live on;		# on|off. Enables this application and allowing live streaming to it. Default=on.
	# max_streams 32;	# Sets maximum number of RTMP streams. Default value is 32 which is usually ok for most cases. Default=32.
	# max_connections 100;	# Sets maximum number of connections for rtmp engine. Default=off. 
	meta on;		# on|copy|off. Receive metadata packets containing predefined fields like width, height etc. Default=on.
	interleave off;		# on|off. Audio and video data is transmitted on the same RTMP chunk stream. Default=off.
	wait_key on;		# on|off. Makes video stream start with a key frame. Default=off.
	wait_video off;		# on|off. Disable audio until first video frame is sent (can cause delay). Default=off.
	drop_idle_publisher 10s;# Drop publisher that has been idle for this time. Only works when connection is in publish mode. Default=off
	sync 300ms;		# When timestamp difference exceeds the value specifiedan absolute frame is sent fixing that. Default=300ms.
	play_restart off;	# on|off. If enabled sends "NetStream.Play.Start" and "NetStream.Play.Stop" every time publishing starts or stops. Default=off.
	idle_streams on;	# on|off. If disabled prevents viewers from connecting to idle/nonexistent streams and disconnects all. Default=on.

## NOTIFICATIONS  - This section has too many options to include in this example config. ##
		## Notifications use HTTP callback to inform subscribers that stream has started. You will need a website that can handle these. ##
		## These option go beyond the scope of this configuration file as it contains lots of info. Please visit this url for more info: ##
		## ##

		publish_notify off;	## on|off. Send "NetStream.Play.PublishNotify" & "NetStream.Play.UnpublishNotify" to subscribers. Default=off
		# on_publish;			
		# on_play;
		# on_record_done';

	## EXEC - Many things are possible using exec. To learn more visit   ##
	## You can either set this here, so all the incoming streams to /live/* get transcoded, or you can push to a seperate recorder app. ##
	## The following lines will take our incoming RTMP stream and transcode it to several different HLS streams with variable bitrates  ##
	## This ffmpeg command takes the input and transforms the source into 4 or 5 different streams with different bitrate and quality.  ##
	## 4 or 5 different streams with different bitrate and quality. P.S. The scaling done here respects the aspect ratio of the input.  ##
	## If you enable adaptive bitrate streams here, make sure to disable the stream-push to /hls below, under # STREAM RELAYING LOCAL # ##

	#exec ffmpeg -i rtmp://localhost/$app/$name  -async 1 -vsync -1		## Transcoding can be enabled here, or by using the abshls application below.
	#-c:v libx264 -acodec copy -b:v 256k -vf "scale=480:trunc(ow/a/2)*2" -tune zerolatency -preset veryfast -crf 23 -g 60 -hls_list_size 0 -f flv rtmp://localhost/hls/$name_low
	#-c:v libx264 -acodec copy -b:v 768k -vf "scale=720:trunc(ow/a/2)*2" -tune zerolatency -preset veryfast -crf 23 -g 60 -hls_list_size 0 -f flv rtmp://localhost/hls/$name_mid
	#-c:v libx264 -acodec copy -b:v 1024k -vf "scale=960:trunc(ow/a/2)*2" -tune zerolatency -preset veryfast -crf 23 -g 60 -hls_list_size 0 -f flv rtmp://localhost/hls/$name_high
	#-c:v libx264 -acodec copy -b:v 1920k -vf "scale=1280:trunc(ow/a/2)*2" -tune zerolatency -preset veryfast -crf 23 -g 60 -hls_list_size 0 -f flv rtmp://localhost/hls/$name_higher
	#-c copy -f flv rtmp://localhost/hls/$name_src;

	allow;		# If you decice to harden these rules, it is adviced to uncomment this line.
	# allow publish;	# Anybody from this local IP address range can stream to this application.
	# allow publish;	# Allow a single IP address to stream to this application (multiple lines with different ip's are possible)
	allow publish all;		# Anybody from any location can stream to this application. Comment this out if you want to use deny publish.
	# deny publish;	# Deny this specific IP address from streaming to this application. Can also be an IP address range.
	# deny publish all;		# Nobody can stream to the server except those that you've set in previous lines.
## PLAYING  ##
	allow play;		# Localhost can play the stream from this application. Must be set if you decide to use push later on!
	allow play;	# Anybody in this IP range can play the RTMP directly.
	allow play all;			# Anybody can play the RTMP livestream directly. Comment this out if you decide to use the next option.
	# deny play all;		# Nobody can play the RTMP stream except those that we've set in lines above it.

	record off;			# off|all|audio|video|keyframes|manual. These options sorta speak for themselves.
	record_path /var/livestream/recordings;	# Folder location that will be used to store the recordings. YOU SHOULD CHANGE THIS TO YOUR PREFERENCE!
	record_suffix -%d-%b-%y-%T.flv;	# Added to recorded filenames. Example uses 'strftime' format results: -24-Apr-13-18:23:38.flv. Default=.flv
	record_unique on;		# on|off. Appends timestamp to recorded files. Otherwise the same file is re-written each time. Default=-off
	record_append off;		# on|off. When turned on recorder appends new data to the old file with no gap. Default=off
	record_lock off;		# on|off. When turned on recorded file gets locked with 'fcntl' call. Default=off.
	record_notify off;		# on|off. Toggles sending "NetStream.Record.Start" and "NetStream.Record.Stop" status messages. Default=off.
	# record_max_size 128K;		# Set maximum file size of the recorded files. No default.
	# record_max_frames 200;	# Sets maximum number of video frames per recorded file. No default.
	# record_interval 15m;		# Restart recording after this number of (milli)seconds/minutes. Zero means no delay. Default=off.
	# recorder name	{}		# Create recorder{} block. Multiple recorders can be created withing single application. Example:
	recorder audio {
		record audio;
		record_suffix .audio.flv;
		record_path /var/livestream/recordings/audio;
	## This will automatically convert your .flv recordings to mp4 when the stream/recording is stopped. WARNING! This will cause high CPU usage!! ##
	# exec_record_done ffmpeg -i $path -f mp4 /var/livestream/recordings/$basename.mp4;

		## For more info please visit: ##
		## pull url [key=value]*	# Creates pull relay. A stream is pulled from remote machine and becomes available locally. ##
		## URL Syntax:			[rtmp://]host[:port][/app[/playpath]] ##
		# pull rtmp:// name=channel_a;		# This is an example. Visit above url for more info.
		# session_relay off;		# on|off. On=relay is destroyed when connection is closed. Off=relay is destroyed when stream is closed. Default=off.

		## Push has the same syntax as pull. Unlike pull, push directive publishes stream to remote server. ##
		## This will push the stream from incoming /live application to these below to create HLS and/or Dash streams, or to record or transcode automatically. ##
		## Only enable HLS push if you are not using ABS options earlier or by using the encoder application ##

		push rtmp://localhost/hls;		# Remember to enable an app called HLS! Disable it if you use transcoding using variable bitrates.
		push rtmp://localhost/dash;		# Remember to create the app dash! It is safe to disable this if you're not interested in using Dash.

		# push rtmp://localhost/encoder;	# Enable this if you're not using the ABS options earlier but want to use a different application for it.

		# push rtmp://localhost/recorder;	# Enable this if you want to record your stream and convert it to MP4 automatically when you stop the stream.

		## Push can also be used to re-stream your stream to other platforms. You can do this directly or use an additional application for this. ##
		## Using an additional local application allows you to set up variables if you prefer. Or you can choose to push to 3rd party directly. ##

			# push rmtp://localhost/youtube;	# Uncomment this to use application on localhost (MUST BE SPECIFIED). OR simply use the following line:
			# push rtmp:// /YOUR-LIVE-STREAM-KEY;	# Your RTMP stream will be pushed as it is to Youtube as an RTMP stream.
			# push rmtp://localhost/twitch;		# Uncomment this to use application on localhost (MUST BE SPECIFIED). OR simply use the following line:
			# push rtmp:// /live_YOUR-LIVE-STREAM-KEY;	# Your RTMP stream will be pushed as it is to Twitch as an RTMP stream.

	application recorder {
	live on;
		recorder all {		
			record all;				# off|all|audio|video|keyframes|manual. These options speak for themselves.
			record_path /var/livestream/recordings;	# Folder location that will be used to store the recordings.
			record_suffix all-%d-%b-%y-%T.flv;	# Added to recorded filenames. Example uses 'strftime' format results: -24-Apr-13-18:23:38.flv. Default=.flv
			record_unique on;			# on|off. Appends timestamp to recorded files. Otherwise the same file is re-written each time. Default=-off
			record_append off;			# on|off. When turned on recorder appends new data to the old file with no gap. Default=off
			record_lock on;				# on|off. When turned on recorded file gets locked with 'fcntl' call. Default=off.
			record_notify off;			# on|off. Toggles sending "NetStream.Record.Start" and "NetStream.Record.Stop" status messages. Default=off.
			# record_max_size 4096M;		# Set maximum file size of the recorded files. No default.
			# record_max_frames 200;		# Sets maximum number of video frames per recorded file. No default.
			# record_interval 15m;			# Restart recording after this number of (milli)seconds/minutes. Zero means no delay. Default=off.
			}					# Recorder closing bracket
	## This will automatically convert your .flv recordings to mp4 when the stream/recording is stopped. WARNING! This will cause high CPU useage!! ##
	# exec_record_done ffmpeg -i $path -f mp4 /var/livestream/recordings/$basename.mp4;

	application encoder {
	live on;
	exec ffmpeg -i rtmp://localhost/encoder/$name  -async 1 -vsync -1
	-c:v libx264 -acodec copy -b:v 256k -vf "scale=480:trunc(ow/a/2)*2" -tune zerolatency -preset veryfast -crf 23 -g 60 -hls_list_size 0 -f flv rtmp://localhost/hls/encoder/$name_low
	-c:v libx264 -acodec copy -b:v 768k -vf "scale=720:trunc(ow/a/2)*2" -tune zerolatency -preset veryfast -crf 23 -g 60 -hls_list_size 0 -f flv rtmp://localhost/hls/encoder/$name_mid
	-c:v libx264 -acodec copy -b:v 1024k -vf "scale=960:trunc(ow/a/2)*2" -tune zerolatency -preset veryfast -crf 23 -g 60 -hls_list_size 0 -f flv rtmp://localhost/hls/encoder/$name_high
	-c:v libx264 -acodec copy -b:v 1920k -vf "scale=1280:trunc(ow/a/2)*2" -tune zerolatency -preset veryfast -crf 23 -g 60 -hls_list_size 0 -f flv rtmp://localhost/hls/encoder/$name_higher
	-c copy -f flv rtmp://localhost/hls/encoder/$name_src;

	application hls {		# We enabled pushing stream from 'live' application to 'hls' we need to define it of course.
	live on;			# on|off. Enables this application and allowing live streaming to it. Default=on.
	hls on;				# on|off. Toggles HLS on or off for this application.
	hls_type live;			# live|event. Live plays from the current live position. Event plays from the start of the playlist. Default=live.
	hls_path /var/livestream/hls; # Location to store the video fragment files. Will be created if it doesn't exist.
	hls_fragment 5s;		# Sets HLS fragment length in seconds or minutes. Default=5s.
	hls_playlist_length 30s;	# Sets HLS playlist length in seconds or minutes. Default=30s.
	hls_sync 2ms;			# Timestamp sync threshold. Prevents crackling noise after conversion from low-res (1KHz) to high-res(90KHz). Default=2ms.
	hls_continuous off;		# on|off. In this mode HLS sequence number is started from where it stopped last time. Old fragments are kept. Default=off.
	hls_nested on;			# on|off. In this mode a subdirectory of hls_path is created for each stream. Default=off.
	hls_cleanup on;			# on|off. Nginx cache manager process removes old HLS fragments and playlist files from hls_path. Default=on.
	hls_fragment_naming system;	# system = use system time. sequential = use increasing integers. timestamp = use stream timestamp. Default=sequential.
	hls_fragment_slicing plain;	# plain|aligned. Plain: switch fragment when target duration is reached. Aligned: switch fragment when incoming timestamp is a
					# multiple of fragment duration. Makes it possible to generate identical fragments on different nginx instances. Default=plain.
	## ENCRYPTION KEYS	## !! Only works if you have certificates defined in your HTTP server block (Usually a seperate file in /sites-available/yourfile.conf)
	# hls_keys on;				# on|off. Enables HLS encryption. AES-128 method is used to encrypt the HLS fragments. Requires ssl module. Default=off.
	hls_key_path /var/livestream/keys;	# Sets the directory where auto-generated HLS keys are saved. Default= hls_path.
	# hls_key_url;	# Sets url for HLS key file entries. When empty it assumes hls_path. Default= empty.
	# hls_fragments_per_key 100;		# Sets the number of HLS fragments encrypted with the same key. 0 means only one key is created at the publish start and 
						# all fragments within the session are encrypted with this key. Default=0.
		## HLS_VARIANT - Used for variable bitrate streaming. Please read: ##
		## When hls_variant suffix is matched on stream name then variant playlist is created for the current stream with all entries specified by hls_variant
		## directives in current application. Stripped name without suffix is used as variant stream name. The original stream is processed as usual.
		## Optional parameters following the suffix are appended to EXT-X-STREAM-INF in m3u8 playlist. See HLS spec 3.3.10. EXT-X-STREAM-INF for full list.

		#	hls_variant _low BANDWIDTH=288000;	# _low		- Low bitrate, sub-SD resolution
		#	hls_variant _mid BANDWIDTH=448000;	# _mid		- Medium bitrate, SD resolution
		#	hls_variant _high BANDWIDTH=1152000;	# _high		- Higher-than-SD resolution
		#	hls_variant _higher BANDWIDTH=2048000;	# _higher	- High bitrate, HD 720p resolution
		#	hls_variant _src BANDWIDTH=4096000;	# _src		- Source bitrate, source resolution

	application dash {		# These variables will be used since we enabled pushing /live stream to this application.
	live on;			# on|off. Enables this application and allowing live streaming to it. Default=on.
	dash on;			# on|off. Toggles MPEG-DASH on the current application.
	dash_path /var/livestream/dash;	# Location to store the video fragment files. Will be created if it doesn't exist.
	dash_fragment 5s;		# Sets DASH fragment length in seconds or minutes. Default= 5s.
	dash_playlist_length 30s;	# Sets MPEG-DASH playlist length. Defaults= 30s.
	dash_nested on;			# on|off. In this mode a subdirectory of dash_path is created for each stream. Default=off.
	dash_cleanup on;		# on|off. Nginx cache manager process removes old DASH fragments and playlist files from dash_path. Default=on.

## YOUTUBE - Only required if you decide to re-stream using this application ##
	#application youtube {
	#	live on;
	#	record off;
	#	allow publish;
	#	deny publish all;
	#	push rtmp:// /YOUR-LIVE-STREAM-KEY;
## TWITCH APPLICATION - Only required if you decide to re-stream using this application ##
        # application twitch {		
	#	live on;
	#	record off;
	#	allow publish;
	#	deny publish all;
	#	push rtmp:// /live_YOUR-LIVE-STREAM-KEY;
        #	}

IMPORTANT! Notice that the variables to enable the use of encryption keys for the stream are not enabled in this config. Also the part that handles the adaptive bit-rate streaming has been commented out as well (lines 84/89). You have to remove these comments if you want to enable ABS here. Before you touch the encryption keys variables make sure that you have created decent certificates before. To enable ABS just remove the # in front of the lines, and place a # at the beginning of the line that says: “push rtmp://localhost/hls;” (on line 138) and then restart Nginx. As an alternative you can leave them disabled, and use push to forward the stream to application /encoder. Everything that comes in at /encoder will automatically be encoded to the stream for ABS. The reason I decided to disable these options is so you can first test the server without it doing any encoding. Then, if that works, you can enable ABS by either using the push option I just mentioned, or by removing the comments I also just mentioned.

See if the config is good (sudo nginx -t) and then do a restart (sudo systemctl restart nginx) and now you’re done with the hardest part. Let’s see if the server accepts your stream. Use your favorite app (Like OBS or Wirecast) and in settings choose to use a custom server under server tab, and enter your server address here manually. Example: RTMP://yourIP/live or RTMP://youdomain/live. Under livestream-key field set: stream
Now start streaming! The fastest way to check if your stream works is to use the stat page at: https://yourdomain/stat or http://serverIP/stat

What your /stat page should look like when you started a stream

One way to check if everything is working as it should at this moment is by using VLC or PotPlayer to open the RTMP stream directly. The address will be the same as you’ve set in OBS/Wirecast with the streamkey added at the end. Like: rtmp://youdomain/live/stream. But you might as well skip this step because we should also have one HLS and one DASH stream that we may as well use to see if all is working. You’ll figure out how to do this yourself. Or not.. in that case just continue with the guide first and get it all over with.

6. Install and configure Video.JS web player

Video.JS is a web-video player that supports adaptive stream playback. It can do a lot more by utilizing plugins. Have a look at their website to get a glimpse of what the possibilities are. You can choose to follow these next few steps and download Video.JS and host the files from our own server, or you can choose to do it the smarter way, by not downloading anything but just pointing to a CDN hosted in your <script> . The smarter way is also faster, so do that. You can find out the exact URL’s of the most recent version of VideoJS on their github. Also the instructions on what to do.

Here’s the old-fashioned way , that I for some reason prefer..

sudo wget -O /var/www/YOURDOMAIN/web/js/videojs/
cd /var/www/YOURDOMAIN/web/js/videojs
sudo unzip

Create a new file in your website root folder called videoplayer.html and paste & edit the lines you see a bit below here. Or simply download it directly from my github and edit it. Whatever you choose, in both cases you will have to carefully inspect the code and replace all the things that are marked to to be replaced.

sudo wget -O /var/www/YOURDOMAIN/web/videoplayer.html
nano /var/www/yourdomain/web/videoplayer.html
<!DOCTYPE html>
<meta charset=utf-8 />
<link href="https://YOURDOMAIN/js/videojs/video-js.css" rel="stylesheet">
  <video-js id="live_stream" class="vjs-default-skin" controls preload="auto" width="1280" height="auto">
    <source src="https://YOURDOMAIN/hls/stream/index.m3u8" type="application/x-mpegURL">
  <script src='https://YOURDOMAIN/js/videojs/video.js'></script>
  <script src="https://YOURDOMAIN/js/videojs/videojs-http-streaming.js"></script>
    var player = videojs('live_stream');

You have to edit 4 lines in there. Make sure Nginx can access all the files.

sudo chown -R www-data:www-data /var/www/yourdomain/web /var/www/html

You are done!! You have successfully setup a live-stream server. Congratulations. You should now be able to see your own live-stream when you open the video player page in your browser. But only if you have of course started streaming.

Now you can have a closer look at your nginx.conf to see what all the variables can do, and edit them if you have specific wishes or needs. When you are streaming with ABS enabled, you should at first try to monitor your server to see how much CPU it will be using and how far you can go with adding other streams. I’ve said it before, encoding will be hard work for your CPU, no matter what generation, so always keep an eye out on what your setup does to your CPU when you are live-streaming, and adjust your config where necessary.


7. Adaptive Bit rate Streaming

When you’re ready to enable Adaptive Bitrate Streaming, you should open the /etc/nginx/nginx.conf file and make a few changes.

sudo nano /etc/nginx/nginx.conf

Remove the hashtags (#) from the lines that enable adaptive bitrate streaming, as shown in the video. Place a new hashtag in front of the default HLS push that I also show in the video. Continue to remove the hashtags from the lines that specify the variant streams. Again, like I show in the video, and in the three images below.

Save the file and test your configuration by using nginx -t. When the results come back successful go ahead and restart nginx like this:

sudo systemctl restart nginx

You are now able to use the adaptive bitrate stream functionality! If you want to show this stream in a locally hosted html file, you can copy the file videoplayer.html that we created earlier, to a new file, like abs.html. Edit this new file and replace the URL that points to the .m3u8 playlist file in it. Change /hls/stream.m3u8 to /hls/stream/stream.m3u8 and save the file.

Now open it in your web browser by going to https://YOURDOMAIN/abs.html. It should show your HLS stream. You could also use the following command to copy it from Github, but you will also need to edit it first to add your domain name.

wget -O /var/www/YOURDOMAIN/web/abs.html
nano /var/www/YOURDOMAIN/web/abs.html
<!DOCTYPE html>
<meta charset=utf-8 />
<link href="https://YOURDOMAIN/js/videojs/video-js.css" rel="stylesheet">
  <video-js id="live_stream" class="vjs-default-skin" controls preload="auto" width="1280" height="auto" poster="https://YOURDOMAIN/poster.jpg">
    <source src="https://YOURDOMAIN/hls/stream.m3u8" type="application/x-mpegURL">
  <script src='https://YOURDOMAIN/js/videojs/video.js'></script>
  <script src="https://YOURDOMAIN/js/videojs/videojs-http-streaming.js"></script>
    var player = videojs('live_stream');

Using Bradmax Player that we’ll be installing later in this guide, it will be possible to manually switch between the different adaptive streams. I believe VideoJS also supports this but I’m not certain it will do so by default. You may also want to have a look at the browse-able folder that contains the video fragment files by entering in your browser: https://YOURDOMAIN/hls. This is where the m3u8 files should be pointing to.

You can stop following this how-to guide here if you like. You did a great job if you completed all the steps up to this point. Your server should now be running. The next steps are totally optional. Continue reading if you’re curious what else we’re gonna do!!

Additional, optional extra steps

8. Installing PHP (7.4 or 8.0)

Stick around if you want to host a (WordPress) website on your server that shows the live-streams. We’ll install PHP, MariaDB, WordPress and of course a nice video player plugin for WP that supports adaptive bitrate streaming playback.Let’s start with installing PHP 7.4.

sudo apt install php7.4 php7.4-common php7.4-fpm php7.4-gd php7.4-mysql php7.4-imap php7.4-cli php7.4-cgi php7.4-curl php7.4-intl php7.4-pspell  php7.4-sqlite3 php7.4-tidy php7.4-xmlrpc php7.4-xsl php-memcache php-imagick php7.4-zip php7.4-mbstring php-pear mcrypt imagemagick libruby memcached

It’s also an option to install PHP 8 instead. It’s really easy we only have to add the Ondrej repository. Like this:

sudo apt install software-properties-common
sudo add-apt-repository ppa:ondrej/php
sudo apt update
sudo apt install php8.0-fpm -y

It might be a good idea to install some common modules for PHP. I’ve put together a collection that is widely used. But feel free to skip this or choose only the ones you need.

sudo apt install -y php8.0 php8.0-common php8.0-gd php8.0-mysql php8.0-imap php8.0-cli php8.0-cgi php8.0-curl php8.0-intl php8.0-pspell php8.0-sqlite3 php8.0-tidy php8.0-xmlrpc php8.0-xsl php8.0-zip php8.0-mbstring php8.0-soap php8.0-opcache php-pear php-imagick php-apcu

Now we need to tell Nginx to use the PHP8.0-fpm sock. Place these lines in a virtual hosts file, or in the nginx.conf.

server {
    # . . . your other code

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php7.4-fpm.sock;
		# fastcgi_pass unix:/run/php/php8.0-fpm.sock;

For Nginx we must edit a specific line in /etc/php7.4/fpm/php.ini. Search the file for “;cgi.fix_pathinfo=1” and replace it with “cgi.fix_pathinfo=0” and save the file. A more easy way to do this is by entering this command. The first one is if you use the default PHP 7.4 version. The second one is if you use PHP8.

sudo sed -i 's/;cgi.fix_pathinfo=1/cgi.fix_pathinfo=0/g' /etc/php/7.4/fpm/php.ini
sudo sed -i 's/;cgi.fix_pathinfo=1/cgi.fix_pathinfo=0/g' /etc/php/8.0/fpm/php.ini

That was the only thing we are required to change. But there are some other things that is advisable to change as well. Be sure to enter your timezone in the last sed option. The first ones below here are for PHP 7.4. Use your brain and just replace 7.4 in them with 8.0 if you want to make these changes and you’re using 8.0.

sudo sed -i 's/upload_max_filesize = 2M/upload_max_filesize = 1024M/g' /etc/php/7.4/fpm/php.ini

sudo sed -i 's/max_input_time = 60/max_input_time = 300/g' /etc/php/7.4/fpm/php.ini

sudo sed -i 's/max_execution_time = 30/max_execution_time = 60/g' /etc/php/7.4/fpm/php.ini

sudo sed -i 's/;date.timezone =/date.timezone = "Europe\/Amsterdam"/g' /etc/php/7.4/fpm/php.ini
sudo systemctl restart php7.4-fpm nginx
sudo systemctl restart php8.0-fpm nginx

9. Installing MariaDB and PHPMyAdmin

Next up we’ll install MariaDB and PhpMyAdmin simply by using:

sudo apt install mariadb-server mariadb-client phpmyadmin -y

Create a symlink to PhpMyAdmin in your web root folder so you can use it

sudo ln -s /usr/share/phpmyadmin /var/www/yourhostname/web/phpmyadmin

Follow the Mysql secure installation steps


Choose whatever suits your needs. Restart mysql once it is done. (sudo systemctl restart mysql) and login to Mysql like this:

mysql -u root -p

Use the following commands to set up a database and database user for WordPress.

grant all privileges on wordpress.* TO 'wordpress'@'localhost' identified by 'YourPassword';

Since we’re at the mysql cli anyways, we might as well do this as well:

use mysql;
update user set plugin='' where User='root';
sudo systemctl restart mysql

There are other guides out there that can show you what you can change in your Nginx configuration to optimize it specifically for WordPress. You should definitely look in to that because many of these options make your server more secure. It is beyond the scope of this guide to go in to that right now. The plan is to write a new guide on this subject in the future some time but until then, google it. 😛

10. Download and install WordPress

wget -O /var/www/YOURHOSTNAME/web/  

cd /var/www/YOURHOSTNAME/web

sudo unzip

sudo mv /var/www/YOURHOSTNAME/web/wordpress/* /var/www/YOURHOSTNAME/web/

The files will be unpacked in a folder so you may want to move al these files to the folder that is below it. In other words, the files should be placed in the root folder for your website.

sudo chown -R www-data: /var/www/yourhostname/web

Now open your browser and enter your site’s domain name. You should see a language list. This is the first step of the WordPress installer. The rest is pretty explanatory. Remember to use the username and password we set a couple of steps ago during the database setup.

Create a new user for yourself and login to your brand new WordPress.

11. All right Sparky! Let’s add a video player

Once the setup is complete, you can log in to your new WordPress website. To add your stream to the site first install a plugin for this purpose. Now there’s plenty of video player plugins out there, but I strongly advice to use Bradmax Player.

You’ll be able to customize your player if you create an account at their website and from there you can create your customized player.

Once you did that you are asked if you want to generate the files for your player. Choose yes and download the zip. Open the zip and extract the bradmax.json file.

Now go to the settings page of Bradmax plugin in WordPress and upload the .json.This way the player on your site will look like what you’ve set up on the Bradmax site.

Now we need to add the shortcode for our player to a page or post. You have several variables you can use for this. Most importantly is the address of the .m3u8 playlist file. See the examples below to see what your options are.

bradmax_video url="url to your file or playlist"
poster="url to your poster file"
autoplay="true or false"
duration="length of video file in seconds"
style="border:solid 1px gray"

Let’s put together a proper shortcode that we can actually use. It can look like any of the examples below.

[bradmax_video url="https://yourdomain/hls/stream.m3u8" poster="https://yourdomain/wp-content/uploads/poster.jpg" style="border:solid 1px gray" autoplay="false"]

The example above points to the playlist of your HLS stream and it also adds a poster image for when there is no stream active when a person opens the player. Also it will add a border and will not automatically start playing.

[bradmax_video url="" style="width:1280px;height:720px;border:solid 1px gray"]

This example sets the player’s resolution in pixels. In this case 1280×720 with a gray border of 1 pixel.

[bradmax_video url="https://yourdomain/static/video/index.mpd" url_2="https://yourdomain/static/video/index.m3u8"]

This example plays a Dash stream and as a fallback it will play a HLS stream. For more info on the shortcode variables that are available, check out the help pages on the Bradmax website.

In case you have any question about this guide, leave a comment below and I’ll respond as soon as I can.

Comments 84

  1. Thank you so much for your effort and for your wonderful explanation, but I really have two questions
    Hello, welcome. I have a live matches streaming website and I have ubuntu 18.4 CPU 6 ram 16 320 SSD, but when using it on the site the number of simultaneous online callers is no more than 800.
    But I want more traffic to my site
    The broadcast is interrupted when I reach 800 viewers online at one time but have more than that number at one time and exceed 6,000 viewers at one time?
    First of all do I have to do load balancing and can I do it this way? Is there an explanation to describe this method and how it works?
    Second, what is installed on the backend servers and what are their steps?
    Third, and this is the most important, which is how to remove channels, not one channel

    1. Post

      So when you reach 800 viewers the stream just stops?
      Did you follow this guide or did you already have it set up?
      What sort of internet connection do you have? Do all the 800 viewers receive the stream from just the one live-stream server? Is the website hosted on the same machine?
      Please tell me more so I can better understand your situation.

      1. My advice on streaming to lots of users is to build a Kubernetes setup with load balancing and also make sure you have enough bandwidth, most Data Centres will throttle you to 1Gbps, maybe an upgrade to 10Gbps adapter will assist. You could always load balance it across multiple regions and then use the nearest hop to reduce cross continent transfers.

        If your really serious you can sign up for nginx plus as that handles it all for you.

        1. You are welcome and thank you very much for your response to my question, but unfortunately I do not own a 10g server, but I have 1g servers. Based on this, I am asking for an explanation of a method for building load balance, but is there an explanation of the method for free video ??
          But all I saw was about load Balance and it was an explanation of how load Balance works on a web server, which I don’t need !!
          I need a way to do load balance using the nginx-rtmp-module, or if in a similar way, but it is easier than it is to skew it and it is free. So please send your explanations videos to her. I remain very thankful and very grateful to you. Thank you again again.

      2. The type of this site is a live broadcasting site for matches, and I followed one of the methods that you explained on your YouTube channel, but I have modified some of the things in it, namely obtaining a free security certificate and installing it on the server This site is to be hosted by Google Blogger because it bears many views at one time, and I am running Ubuntu 18.4 server and install nginx-rtmp-module + hls, but the number of views at one time is the biggest problem for me, which made me turn to the load balane command Will this free method come with me with what I have requested or not, which is what I am looking for currently, but I found many models on live match broadcasting sites as well, but using php + apache and this method enables them to extract more than one channel at a time by placing pages in it M3u8 drives each page with a channel name
        And also, in addition to this method, which is how the load balancing the number of views at one time, how is it done, what is installed on the back-end servers, and what tools are needed to make this method, which is what I want and I asked you about it Or in other words, I want to build a live broadcast server that can handle the number of simultaneous views of 10k and also support the extraction of a number of pages or channels and also distribute the loads on the backend servers and what is the way to build them At the end, I would like to extend my sincere gratitude and gratitude to you for your patience in accepting my question

    2. Greate!

      I have one question… what is the bandwidth that your server consumes for one match?

      For Ubunt+6 Cores+16GB RAM+ 320GB SSD It is not to bad.

  2. Hi Andre,
    so glad you posted this video… I have managed to get the multi HLS streams going as they show up in the /stat page… however i cannot get the player to work on the /abs.html page….
    do i need to install a plug-in for videojs to play the multi hls streams??

    1. Post

      Did you watch both the video’s? It’s split up in two episodes, and in the second one I talk about this subject.

      I can imagine that you will want to see your live-stream in a player that shows the current resolution/bandwith and / or you can manually switch between them. And you will! Easily…
      The stream should work from the abs.html file using VideoJS, but only if you enabled the adaptive bitrate streaming config lines in nginx.conf, as shown in the second video. The only difference between videoplayer.html and abs.html is the location of the .m3u8 file. For the rest they are the same.

      The only reason for using a plugin for VideoJS would be to show the buttons to manually switch between the different variant streams in the videoplayer. The free to use JW player also shows this, and so does the Bradmax Videoplayer, Clappr also, and I’m sure plenty of more players can do it also.

      By default the server will use this output structure: yourdomain/live/stream/index.m3u8 while the adaptive one will use yourdomain/live/stream.m3u8. The stream .m3u8 is a playlist that VideoJS can play perfectly, and you should actually not notice if its a regular single HLS stream, or if it’s adaptive. The player automatically switches between the variants depending on your bandwith and type of device/screen.

      Another option to test your streams would be to use a free to use 3rd party tool like this hls.js player right here:
      An added bonus to using a tool like that is that you’ll get to see some interesting additional statistics.

      Good luck! Let me know if you managed to get it running please. If not, I could help.

      1. Hi,
        I did get everything to run!! However I am having buffering issues when I use the ABS config. It could be my computer I am running OBS on…??
        My servers (RTMP/NGINX) are on digital ocean , so I don’t think that is the problem…
        The set up works perfectly with the index.m3u8 config…

        1. Update!
          Your instructions ( if followed), are perfect!!
          I did further testing with a bigger droplet on digital ocean and it resolved tge buffering issue… you did mention at the very beginning tge prerequisites for the server… doh!
          Thank you again for great work. I very much appreciate it.

          1. Post
        2. Post

          I have also tried using cheap VPS servers to use as live-stream server but always experienced delay or other performance issues. By now I’ve learned that any server that is virtualized will always perform less good as a dedicated server. Usually the lower end VPS server share their hardware with who knows how many other VPS users. You would think you would get the full processing power that they offer you. But truth is that when you continuously max out the CPU on your VPS, they’ll probably contact you or take it offline. In my opinion you would be better of with a server at your home that does all the encoding, and use the VPS servers to distribute the stream, either directly or through a CDN. But having said that, I’m glad that it works for you now! How is the performance of the server? Are you maxing out the CPU or is it big enough to handle it with ease?

          1. because it basically does not bear the number of simultaneous views that exist on my site, but I am trying to reach a way to do load balance to the fact that the site increases in strength because I am now spending it from my own pocket and since the dedicated servers are mentioned, they are In most companies their numbers are very, very, very large for me, and this of course is not the difference in the currency 🙂 So I work on the small servers.

          2. Hi,
            I still haven’t thoroughly investigated the VPS bandwidth sitch yet… I am exploring getting a M1 mac mini and putting it in a data-center… i’ll update the progress with that as it happens….
            i am now wrestling with having a stream auto-start in player without having to refresh the webpage …. any ideas?? can i use the /stat page info??

  3. Andre, The only small thing I would add is the nginx upload directive, it can be put after the Index index.php etc this is in addition to the PHP upload limit you recommend using sed.
    server {

    root /var/www/;
    index index.php index.html index-nginx.html index.htm;
    client_max_body_size 8192M;
    add_header Strict-Transport-Security “max-age=63072000;”;
    add_header X-Frame-Options “DENY”;

    client_max_body_size 100M;

    Apart from that, absolutely first class instructions and all running great, I will update you on doing all of this on the blockchain. I am building my kubernetes cluster in IBM’s Softlayer London DC and will advise.

    thank you for your wonderful sharing.

    1. Post

      Thanks for your input! You’re absolutely right there. I want to change the guide and include it. But I’m not 100% sure where to put it as your example is showing two different client_max_body_size

        1. Post
  4. Hello, first of all thank you very much for this video tutorial and the explanations. They are really very useful to me.
    I want to make you a query. I’m in step 6, I have seen that the git link the updated video.js version is 7.12.1, while the one you have in the tutorial is 7.11.4.
    What do you recommend me? Do I use 7.12 or 7.11? Because what I do not know if it can give compatibility problems. Thank you. Greetings.

    1. Post

      Go for the most recent version. I will not , as far as I know, cause any issues when it comes to compatibility to show your livestream. The version I showed, was the most recent one at the time of making, and I did not have any specific reason for choosing that version other then it being the most recent one. Hope that answers your question.

  5. Hi Andre and Thank you for all you r terrific work you have been doing til now. You are of great help to me.

    I have a big problem because if I do not define this quickly I might have serious issuess at work.. so please if you can help it would be of greatly appreciated……

    I need to set this kind of projetc where at the end I will stream from vmix into this Nginx server with rtmps throu video Js Player…

    …now I am at the point that when I test the /etc/nginx/sites-enabled/ file with nginx -t it stops telling me that : nginx: [emerg] unknown directive “rtmp_control” in /etc/nginx/sites-enabled/
    nginx: configuration file /etc/nginx/nginx.conf test failed. I tried loading the module into the conf.. but it does not work….

    I followed all your indication making a Google Cloud Virtual Machine and installing it all as you told me….

    what am I doing wrong?

    1. Hello Manuel … I also had the same problem that you mentioned, it is very simple, you just have to place the line where the error is commented, in your case it is “24” put the full domain
      This is how you find the file: /etc/nginx/nginx.conf
      include /etc/nginx/conf.d/*.conf;
      include /etc/nginx/sites-enabled/*.conf;

      You must place like this:
      include /etc/nginx/conf.d/*.conf;
      include /etc/nginx/sites-enabled/youdomain.conf;

      I hope I have helped you, I am learning this is new for me and I like it … very well explained

      1. Post

        Oh shh.. I noticed this response after posting mine 🙂
        Good point John. I’m curious if he ever got it working after your comment… 🙂

        1. Hi Andre … I will tell you that if it is working very well, but I would like to know how many users are connected to my ngnix server, could you please give me a hand with it, I am new to this and my knowledge is basic, but how do you do it? you describe I get you

          1. Post
    2. Post

      Sorry for responding this late…

      The error message you mention: [emerg] unknown directive “rtmp_control” tells us that Nginx does not know what to do with this directive. My first thought would be that you may have not installed the RTMP module for nginx correctly. If however you would confirm that it is installed, then you could try to remove these directives from your nginx.conf file, and see if that fixes the issue. Hope this helped you, if not, respond, so we can pick it up from there.

  6. Hello, I have a problem. I use OBS for stream to the server but, OBS give me a message that says “Could not connect to server. The connection timed out. Try a different server, or check that the connection is not being blocked or other security software”, and i tried with youtube and twitch and works perfectly, i am amateur on this area, i don´t know what happens.

    PS: i made the server on google cloud with ubuntu 20.10

    1. Post

      Again, sorry for the late response. My bad.

      Your problem description is not enough for me to pinpoint out directly what the issues could be here. I will need some more information. Please use the contact form to provide me with as much details as you can.

    1. Post

      You can set whatever you want as streamkey, as long as you make sure you set it in nginx.conf, and use the same key in the address that you want to stream to eventually. There are also free packages to be found on that allow you to set different keys for different people, that are usually randomly generated so a lot more secure.

    1. Post

      Dash also supports adaptive bitrate streaming. But to be honest I’ve never really looked into it any further so I can’t really say what must be done to actually use this functionality. But it’s interesting that you brought it up cause it’s something I should really look into, and perhaps make a new video on it 🙂 So thanks

  7. Thanks for your very good tutorial. I found this problem.

    ubuntu@ns532072:/usr/src$ sudo certbot –nginx -d s****.com
    Saving debug log to /var/log/letsencrypt/letsencrypt.log
    Plugins selected: Authenticator nginx, Installer nginx
    Obtaining a new certificate
    Performing the following challenges:
    http-01 challenge for s****.com
    Waiting for verification…
    Challenge failed for domain s****.com
    http-01 challenge for s****.com
    Cleaning up challenges
    Some challenges have failed.

    1. Post

      Does the domain point to the IP from the server? Is it reachable from the outside? Meaning friom the internet? Letsencrypt will only work of the domain is set to the ipthat you are making the request from.

    2. Certbot gives you error because find issues on the DNS. You need to register your DNS with the correct IP address, A Zone and CNAME Zone, pointing to your domain. This can be done directly by you on your registrar provider where you bought your domain name.

  8. Thank you very much Andre … For your effort, time and patience in demonstrating everything step by step ….. It works perfect for me !! the first part I had some problems with some errors when validating the service with “nginx -t” but I was able to solve them.

    A query as I can lower the delay or latency of what I am transmitting there is a delay of almost 26 seconds, I used to do it in a simple way I installed only the services “nginx” and “libnginx-mod-rtmp” I configured the configuration file and it transmits and the delay is 1 to 2 sec, it is very important for me to leave it at that time I am using the same test server here the characteristics of my server (DELL power edge SC1435 / Quad Core AMD Opteron 2354 2.2 Ghz 64 bits with 4GB of Ram and 120 SSD) I hope I can count on your help and thanks again

    1. Post

      Streaming using rtmp will always have some delay, and especially when converting the stream to HLS since the server has to do some work on that, and that just takes time. But 26 seconds is an amount that can be lowered. Lowest delay I ever heard of was like 6 seconds. It does require some tweaking and messing around with 2 or 3 directives in your nginx.conf. If you need more help from me on this , please send me a message using the contact form, so we can mail each other. Easier to communicate that way 🙂

      1. Hi Andre … I realized that using hls it has a high latency 26 to 35 sec but if I see it directly through vlc the time is 1.5 sec which is acceptable, now how can I monitor the users that are connected to my ngnix server Could you please give me a hand with it, I am new to this and my knowledge is basic, but as you describe it I get you

  9. Hello UStOoPiA’s,
    I read your documents on how to install and configure RTMP for live video streaming. Now I am building one for my church but needed to ask you this question. I want to be able to build both front-end and back-end. The front-end will be the ones I can send to my guests or those I am inviting to my meeting, while the back-end is where I will be able to create an account to send to the people and also be able to see the number of those that watch. I am on Raspberry pi 4 using the Ubuntu 20.04 OS. Please guide me well on this .. thanks. Again will need your updated “step-by-step” instructions to build mine. Watching your video lots of piece of steps that throw me off and not sure how to proceed. Please I need this to be able to have one for my church. Thanks

  10. Hello Andre,
    Thanks for the good work. Just a quick one. I installed php both versions, however when i open my localhost/index.php. The files just downloads instead of opening the page. What settings could i be missing because it means php isn’t connected to nginx

    1. Post

      Does your nginx.conf or virtual hostname config file include the directive for where it can find the mime types file? Did you change the directive in php.ini so it says cgi.fix_pathinfo=0 instead of ;cgi.fix_pathinfo=1 ?

      1. Post
        1. Hi Andre,

          your welcome mate, if most people follow all your hard work as is published and then use their brain to engage and expand the knowledge the world would be a better place lol. Its the least I can do to say thanks for the great series.

  11. Dear guy how are you all… hope well…

    I suggest to have all a bear in group and to found a fucky passionate it group… i see you are dutch Andre.. I suppose…

    I have a question fro you.. can you please help me in finsind a way to make the new Video JS player responsive that auto adjust its size while streaming live … depending if its playing from desktop (with more real estate) or if its playing from mobile phone screen…. I did not find a good esthetic solution so far.

      1. Thanks for the guide – it works ALMOST perfectly fine.
        Multiple RTMP push to the NGINX – perfect
        Then the problem starts
        If I only activate a single RTMP push to NGINX the HLS plays plays PERFECT
        However the moment I add another RTMP push I get glitch and buffering
        Any fix or suggestions for this?

  12. Thank you for this most excellent guide, it is working perfectly for me!

    I am using this server as a recipient of another nginx with rtmp module from which I have several incoming cameras. I tried pushing one of them to this server and can view it on the wordpress sites videopplayer.

    I am now trying to figure out how to best add a second page that I send a second stream to, any pointers on that? I have setup a new wordpress page where I can view the same stream – but am having trouble to get two pages to show one video stream per page.

    Any pointers?

    1. Hi Anders

      Does your second, third etc RTMP ingest to the NGINX stutters affecting the HLS streams?
      One RTMP works perfect for me but the moment 2nd or 3rd RTMP – all HLS streams are affected.
      Try using Clappr player instead – working perfectly for me.
      Not sure if links can be posted here.

  13. Thanks for the guide – it works ALMOST perfectly fine.
    Multiple RTMP push to the NGINX – perfect
    Then the problem starts
    If I only activate a single RTMP push to NGINX the HLS plays plays {ERFECT
    However the moment I add another RTMP push I get glitch and buffering
    Any fix or suggestions for this?

    1. Post

      My first thought on what the cause of this issue could be is that it is hardware related. Transcoding takes a lot of processing power. Did you monitor the cpu usage of the server when adding the extra streams? If not, please try this to see wether the cpu is able to handle it or not.

  14. Great guide, thank you so much for this resource.

    I was wondering what the course of action would be for multiple streams/channels?

    1. Post

      That can be accomplished by editing the nginx.conf. There are many resources on the web that explain how to properly setup a nginx.conf that suits to your needs. If this seems overwhelming or complicated to you, send me an email and describe in as many details as you can what you want to accomplish, so I can perhaps help you out a bit.

  15. Hi,
    Everything works fine except my videojs player only shows 2 streams… “ auto and undefined”
    I am using videojs-hls-quality -selector plug-in…..
    When I apply a different src( from the plug-in demo) the video player works perfectly… showing multiple resolution options

  16. Hi, great job with this tutorial. One question, can you put the server url and not the domain in the web page? https: //IP_server/hls/stream.m3u8
    Thank you.

  17. Good job Andre. One question, how can I play the recording of the Recorder application? The idea is that after the live broadcast the user can see the video again when the conversion to mp4 is finished. Like on Youtube for example. Thank you very much.

  18. How can i authenticate a user with a streamkey so that the stream becomes the hls url

    1. It’s not the first time that I’ve been asked this question. And at some point I even made a video on how to accomplish this with rtmp-monitoring. But this was at the same time a new Ubuntu was released and since then the rtmp-monitoring package does not work anymore. So I never published this video. I searched the internet several times trying to find an alternative, but no luck so far. Well except of course for the built-in stat pages from Nginx. Have you tried that already? That might be exactly what you are looking for. Although it’s rather limited, it does show you the amount of streamers and streams etc. I’ll send a screenshot of what it looks like along.
      If you decide to try this, make sure to set the next variable in your nginx.conf file to this: worker_processes 1; If you don’t, you will still see the stat page, but it won’t be accurate. Besides this built In solution I’m afraid I don’t know of any alternatives. If you do find something,. Please let me know also 🙂

      Edit: I just did another attempt to search for an alternative. I found this :
      Perhaps worth trying?

  19. hi Andre thnx for this
    i have 10 gbps server i do
    worker_processes 16;
    worker_connections 768;

    on upload stat Reach 10 gbps with 6k clients ipv4
    how can i know it DDOS or real users
    Because I think I’m attacking, I don’t think it’s real views
    How did you take action against DDOS
    thank u very much

    1. Post

      I would start by carefully investigating your log files. When the logs confirm DDos attacks, the first thing I would do is to implement a third party for providing you ddos protection service. Like for example cloudflare. ( They are very popular, but alternatives also exist.

      1. I have used cloudflare when exiting to Web 2.0 and I also use my own blockchain router to scale. I have 70mbps behind my residential broadband and can serve 50k users on i7 4790 cheap server due to blockchains multiplexing. Cloudflare will stop ddos dead.

  20. ddos with cloudeflare it must be proxied
    when my domain proxied cloudeflare will detect “.ts.” file then ban me for stream violence (soccer)

    1. Post
  21. ok i will see it
    if u can Andre I want to stream 480p with -bv 600 kb with good image if it can
    i test with this :

    ffmpeg -re -i “****” -max_muxing_queue_size 9999 -c:v libx264 -vf “scale=854:trunc(ow/a/2)*2” -preset veryfast -vb 550k -maxrate 550k -bufsize 600k -vprofile baseline -vlevel 3.1 -x264opts keyint=52:no-scenecut -r 26 -c:a aac -b:a 70k -ar 44100 -ac 2 -f flv rtmp://****

    but not good stream but i must stream with 600kb with 480p
    and thnx

    1. You are using the filter expression -vf “scale=854:trunc(ow/a/2)*2” for an input video without a defined aspect ratio: The expected result is a distorted output file.

  22. Post

    I’m confident that James is spot on here!

    I just wanted to try out your command myself, but I get an error message that says: Server error: audio… The rest of the message is unreadable because ffmpeg overwrites the line that holds the error. If I had loads of time, I would investigate further, but unfortunately that’s not the case. And besides, I believe that James already helped you out.

    1. Post

      Yes this may very well be a DDOS attack.

      Here’s a description of the ksoftirqd process:

      Your machine communicates with the devices attached to it through IRQs (interrupt requests). When an interrupt comes from a device, the operating system pauses what it was doing and starts addressing that interrupt.
      In some situations IRQs come very very fast one after the other and the operating system cannot finish servicing one before another one arrives. This can happen when a high speed network card receives a very large number of packets in a short time frame.
      Because the operating system cannot handle IRQs as they arrive (because they arrive too fast one after the other), the operating system queues them for later processing by a special internal process named ksoftirqd.
      If ksoftirqd is taking more than a tiny percentage of CPU time, this indicates the machine is under heavy interrupt load.

  23. Securing Your Video Streams

    I created my own server nginx
    I put the live link on my WordPress site
    But when I broadcast live on my site and when browsing the source code on the site, I find the server link

    Since every video has a URL, in theory someone could simply copy your video URL and use it with a player of their own choice on a different site.

    How do I hide my server link on my site?
    Please help because I am
    For two days I searched and did not find a solution

Leave a Reply

Your email address will not be published. Required fields are marked *