Setup Nginx on Ubuntu to Stream Live HLS Video

Updated on November 21, 2023
Setup Nginx on Ubuntu to Stream Live HLS Video header image

HTTP Live Streaming (HLS) is a very robust streaming video protocol implemented by Apple Inc. HLS uses HTTP transactions which traverse firewalls, proxies, and can be distributed through CDNs with ease. Therefore, this technology is able to reach a much larger viewing audience than RTP or other UDP-based protocols. Much of the live streaming video online today is hosted by expensive systems which use HLS, but they are generally expensive and require a lot of server resources. This tutorial will show you how to set up a very affordable Ubuntu 14.04 VULTR VPS to do HLS live streaming events using only open source software.

The first step is to read and set up your server according to the Vultr Doc "Setup Nginx-RTMP on Ubuntu 14.04". The instructions in that doc were the most comprehensive for the setup stage, at the time of writing. I would mention that you may wish to substitute "nginx-1.7.5" with "nginx-1.9.4" or whatever the latest version of Nginx is available. The other suggestion is to compile Nginx with the HTTP stub status module in order to give yourself the ability to monitor how many live HLS viewers you have later.

Instead of compiling with:

./configure --with-http_ssl_module  --add-module=../nginx-rtmp-module-master

Use this string in the procedure outlined in the prerequisite doc:

./configure --with-http_ssl_module --with-http_stub_status_module --add-module=../nginx-rtmp-module-master

The example in this tutorial will create both "live" and "mobile" (optimized) streams and will use ffmpeg (installed in the previous tutorial) to generate the bit-rate adjusted, mobile-optimized HLS stream. The example will also show how to cause the server to record your live streams automatically and to allow you to play the recordings back as a video on demand (VOD) replay service.

First, create the folder structures necessary to hold the live and mobile HLS manifests and video fragments:

sudo mkdir /HLS
sudo mkdir /HLS/live
sudo mkdir /HLS/mobile
sudo mkdir /video_recordings
sudo chmod -R 777 /video_recordings

It's probably a good idea to have your firewall turned on if you haven't done so already. If so, you must allow traffic into the ports used by Nginx and HLS. If you'd like to run without the firewall for now, ignore the ufw section below.

sudo ufw limit ssh
sudo ufw allow 80
sudo ufw allow 1935
sudo ufw enable

HLS streaming requires a significantly different Nginx configuration from the RTMP configuration in the first article. Edit your nginx.conf file to use the following, substituting "my-ip" and "my-stream-key" with your info. You can use anything you would like for "my-stream-key" it's just a word which is unique and helpful to you. You may wish to back up your original config file first, then paste my supplied configuration info into the editor, replacing everything that was there:

sudo cp /usr/local/nginx/conf/nginx.conf /usr/local/nginx/conf/nginx.conf.original
sudo nano /usr/local/nginx/conf/nginx.conf

New nginx.conf:

worker_processes  1;
error_log  logs/error.log debug;
events {
worker_connections  1024;
}
rtmp {
server {
listen 1935;
allow play all;

#creates our "live" full-resolution HLS videostream from our incoming encoder stream and tells where to put the HLS video manifest and video fragments
application live {
allow play all;
live on;
record all;
record_path /video_recordings;
record_unique on;
hls on;
hls_nested on;
hls_path /HLS/live;
hls_fragment 10s;
	
#creates the downsampled or "trans-rated" mobile video stream as a 400kbps, 480x360 sized video
exec ffmpeg -i rtmp://192.168.254.178:1935/$app/$name -acodec copy -c:v libx264 -preset veryfast -profile:v baseline -vsync cfr -s 480x360 -b:v 400k maxrate 400k -bufsize 400k -threads 0 -r 30 -f flv rtmp://192.168.254.178:1935/mobile/$;
}
	
#creates our "mobile" lower-resolution HLS videostream from the ffmpeg-created stream and tells where to put the HLS video manifest and video fragments
application mobile {
allow play all;
live on;
hls on;
hls_nested on;
hls_path /HLS/mobile;
hls_fragment 10s;
}
	
#allows you to play your recordings of your live streams using a URL like "rtmp://my-ip:1935/vod/filename.flv"
application vod {
play /video_recordings;
}
}
}


http {
include       mime.types;
default_type  application/octet-stream;
	
server {
listen 80;
server_name 192.168.254.178;

#creates the http-location for our full-resolution (desktop) HLS stream - "http://my-ip/live/my-stream-key/index.m3u8"		
location /live {
types {
application/vnd.apple.mpegurl m3u8;
}
alias /HLS/live;
add_header Cache-Control no-cache;
}

#creates the http-location for our mobile-device HLS stream - "http://my-ip/mobile/my-stream-key/index.m3u8"		
location /mobile {
types {
application/vnd.apple.mpegurl m3u8;
}
alias /HLS/mobile;
add_header Cache-Control no-cache;
}  	
	
#allows us to see how stats on viewers on our Nginx site using a URL like: "http://my-ip/stats"		
location /stats {
stub_status;
}

#allows us to host some webpages which can show our videos: "http://my-ip/my-page.html"		
location / {
root   html;
index  index.html index.htm;
}	
}
}

Press Ctrl + X to exit. Say "yes" to save the changes.

You can find clear instructions and examples of the variables possible in this nginx.conf file if you query your favorite search engine for "nginx-rtmp directives". I've been using nginx-rtmp with HLS for some years now, without using the "allow publish" and "deny publish" directives and I've seen zero instances of people using/invading my video servers. So I did not include those directives here. Read about and add these directives if you would like.

After changing the nginx.conf file, you must restart Nginx to use the new configuration:

sudo service nginx restart

Watch closely for any Nginx error messages and address any errors which may have been caused by miss-spelling, folder ownership, or permissions issues. If you have no error messages, then you're ready to create your encode stream.

You must have a video encoder in order to create the stream. I use OBS (Open Broadcaster Software) - which is open-source and works well for me. There are other solutions to choose from, which are outside the scope of this tutorial. I will not cover everything about configuring an RTMP video encoder. They all require roughly the same input variables though. The key settings you'll need to input in order to use my exact nginx.conf configuration and to function well across most players/browsers/platforms are as follows:

  1. Encoder-x264
  2. Variable bitrate (not CBR or Constant Bit Rate), Quality highest
  3. Max bitrate-600kbps
  4. Audio-Codec-AAC
  5. Audio-Format-44.1khz
  6. Audio-bitrate-64kbps
  7. FMS URL-"rtmp://my-ip:1935/live"
  8. Stream Key-"my-stream-key"
  9. Resolution-640x480
  10. FPS (frames per second)-30
  11. CFR (Constant Frame Rate) - Yes
  12. Keyframe interval-2 seconds (one keyframe every 2 seconds)
  13. x264 Encoding Profile-baseline (may work with main—depends on player used)
  14. x264 CPU Present-veryfast

I recommend trying different encoders and experimenting with them. You may desire a wide aspect ratio-or your camera (or other broadcast) materials may demand it. If so, be sure to change this info in your encoder and also the aspect ratio listed in the exec-ffmpeg section of the nginx.conf file which I've supplied; otherwise you'll get some goofy looking video feeds.

Once your encoder is set up, you can test it all. Start the encoder up with your webcam or some kind of test-fodder running on it. You can view your broadcast at this point with VLC player using URL's such as:

http://my-ip/live/my-stream-key/index.m3u8
http://my-ip/mobile/my-stream-key/index.m3u8

These are for your main and your mobile video streams, respectively. Substitute your IP and stream key accordingly.

After you've successfully broadcasted your first stream check (via ssh or ftp) that your live broadcast was recorded in the /video_recordings folder on your Vultr VPS. You can also try playing this recorded file in VLC with a URL like:

rtmp://my-ip/vod/filename.flv

Nginx stats are also available (with Nginx stub_status). To view visitor/viewer stats, access:

http://my-ip/stats

In order to view your video on a webpage you'll need an embeddable player. There are many embeddable players available which will play HLS video. I have used JW Player for some years now, but the free version will not play HLS. Flowplayer and Bitdash by Bitmovin (among other solutions) are generous enough to offer a non-commercial version of their players for free which will render your HLS stream embedded in a webpage. For this article, I have tried them both and found them both to work quite well with my Vultr/Nginx-based video server. I will briefly show how I got Flowplayer going with my testbed machine.

If you are going to use live HLS streaming in any regular, sustained, or commercial way, I would encourage you to purchase a license from Flowplayer or whichever player you decide to use. You will get a player which has fewer restrictions, more features, and can be branded to your organization. You'll also get support - which can be very important. Other than the Vultr VPS, this is really the only cost associated with the project.

Before you do anything else, it's important to take care of what is called "cross-domain" restrictions, which would otherwise shut down your ability to stream to a webpage/website. Create a crossdomain.xml file in your nginx/html folder and put instructions in it to allow data to flow between domains:

sudo nano /usr/local/nginx/html/crossdomain.xml

First copy (from this page) and then paste (right-click) into the nano editor field the following XML data:

<?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>

Press Ctrl + O to write out, then Ctrl + X to save the file to disk and exit.

I grabbed the HTML5 (version 6.0.3) of Flowplayer which gave me a number of files and a folder when unzipped. I uploaded all the files and the folder into a subfolder of Nginx/HTML root folder which I named "flowplayer". The exact path was /usr/local/nginx/html/flowplayer.

In order to test our video when embedded in a webpage, create a couple of HTML files in the root of the Nginx/HTML folder using nano and populate them with the following contents.

File:

sudo nano /usr/local/nginx/html/hls.html

HTML code:

<!doctype html>
<head>
<link rel="stylesheet" href="flowplayer/skin/functional.css">
</head>

<body>
640x480 664kbps (live) Desktop Browsers<br>
<div style="width:640px;" class="flowplayer" data-swf="flowplayer/flowplayer.swf" data-ratio="0.75">
<video>
<source type="application/x-mpegurl" src="http://my-ip/live/my-stream-key/index.m3u8">
</video>
</div>
<br><br>
480x360 464kbps (mobile) Mobile Browsers <br>
<div style="width:480px;" class="flowplayer" data-swf="flowplayer/flowplayer.swf" data-ratio="0.75">
<video>
<source type="application/x-mpegurl" src="http://my-ip/mobile/my-stream-key/index.m3u8">
</video>
</div>
</body>

File:

sudo nano /usr/local/nginx/html/hls_progressive.html

HTML code:

<!doctype html>
<head>
<link rel="stylesheet" href="flowplayer/skin/functional.css">
<style> .flowplayer { width: 640px; } </style>
</head>

<body>
<div class="flowplayer" data-swf="flowplayer/flowplayer.swf" data-ratio="0.75">
<video>
<source type="application/x-mpegurl" src="http://my-ip/live/my-stream-key/index.m3u8"> 
<source type="application/x-mpegurl" src="http://my-ip/mobile/my-stream-key/index.m3u8">
</video>
</div>
<br>
</body>

Be certain to substitute "my-ip" and "my-stream-key" with your data.

You must substitute the path flowplayer/skin/functional.css for the "#" sign in the stylesheet href="#". The tutorial software strips out the path. You must fill in a URL in the src="" part - it is being stripped out by the tutorial software, use http://my-ip/live/my-stream-key/index.m3u8 and http://my-ip/mobile/my-stream-key/index.m3u8, respectively. You also must add script src tags in the head for both jquery-1.11.2.min.js and for flowplayer.min.js. The tags are being stripped out by the tutorial software. You can find how to fashion these tags by looking at the example html file included with the Flowplayer kit.

Start the encoder up with your webcam or some kind of test-fodder running on it. You can view your broadcast in a browser using the following URLs:

http://my-ip/hls.html
http://my-ip/hls_progressive.html

The first page simply shows each of your streams - the main one and the mobile platform one. You will be able to start them both up and view them. You'll notice that the mobile platform stream is smaller in size. These steps are just to make sure that everything is running.

The second page shows a single stream. But it is set up in a way which is called "progressive streaming". What this means is that when a viewer plays the video, both the server and player try to give the viewer the high-bitrate, high-quality stream if the viewers connection can sustain it. If their connection is poor and it cannot sustain the connection, it rolls down to showing the lower-bitrate (mobile) stream which is 200 kbps lower in bandwidth. If you wished, you could create more graduated, progressive streams with ffmpeg and configure them using my example.

I have tested this configuration on a single core, 1GB Vultr VPS and I found that with a single live stream, plus the trans-rated mobile stream it creates with ffmpeg, the cpu usage was less than 35% and it had consumed only 100MB of my 1GB of ram. Now that's a very efficient setup.

If you plan to stream video which is much larger in pixel dimensions than SD, you may find that you have to use a more powerful VPS. I was curious about this and ran my main stream up to 1280x960 pixels and 2Mbps - putting additional load on things. But the VPS still only used 50% CPU and memory use remained at 100MB. I added a dozen viewers/browsers and it made almost no difference in load on the VPS—only the bandwidth used went up. I'm sure that creating additional trans-rated streams with ffmpeg would continue to put additional load on the system.

I've used this type of server in production environments with between 50 and 100 concurrent users and found that the resource usage went up very little with increasing viewer connections. Experiment with it. But I think that most users would be happy using a single-core, 1GB VPS. Enjoy your new HLS streaming server!