Skip to content

Instantly share code, notes, and snippets.

What would you like to do?
Secured HLS setup with Nginx as media server

Secured HLS setup with Nginx as media server

This example is part of this article.

This is an example for an HLS delivery with basic security. Nginx compiled with nginx-rtmp-module & secure-link is used as media server. Features:

  • Domain filtering
  • Referrer filtering
  • Embed buster
  • Session token for playlist, segments and AES keys
  • AES encryption
  • HTTPS only

Throughout this example the host is assumed to be if you want to use this configurations, be sure to replace all instances of with your domain.

Compiling Nginx

# install deps (Ubuntu)
sudo apt-get install -y build-essential libpcre3 libpcre3-dev libssl-dev

tar -xf nginx-1.10.1.tar.gz
cd nginx-1.10.1

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

make -j
sudo make install
# nginx is now installed in /usr/local/nginx

Pushing video to Nginx

In order to push video to nginx i'm going to use ffmpeg which well supports RTMP as its output. I'm going to create an I frame roughly every 2 seconds which will allow nginx to achieve the 4s segment target. For simplicity i'll be using a static mp4 file and ingest it in infinite loop.

ffmpeg -hide_banner \
-stream_loop -1 \
-re -i test-video.mp4 \
-c:a aac -c:v h264 -g 48 \
-f flv rtmp://localhost:1935/show/live

I'm using live as the stream name, the output hls will carry that same name - e.g. live.m3u8.

Generating Session token

The session token is based on this format (note the spaces): MD5("EXPIREY_DATE_IN_SECONDS CLIENT_IP_ADDRESS SECRET")

here are several examples of generating the token:


get_customer_url() {
  local IP=${1:-}
  local EXPIRES="$(date -d "today + 30 minutes" +%s)";
  local token="$(echo -n "${EXPIRES} ${IP} ${SECRET}" | openssl md5 -binary | openssl base64 | tr +/ -_ | tr -d =)"
  echo "${token}/${EXPIRES}/live.m3u8"

get_customer_url "uigfp(@#tfpIUDGPFiouGDF"

Node.JS (Javascript)

var crypto = require('crypto');

function generateSecurePathHash(expires, client_ip, secret) {
    if (!expires || !client_ip || !secret) throw new Error('Must provide all token components');

    var input = expires + client_ip + ' ' + secret;
    var binaryHash = crypto.createHash('md5').update(input).digest();
    var base64Value = new Buffer(binaryHash).toString('base64');
    return base64Value.replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');

function getStreamUrl(ip, secret) {
    const expiresTimestamp = new Date( + (1000 * 60 * 30)).getTime();
    const expires = String(Math.round(expiresTimestamp / 1000));
    const token = generateSecurePathHash(expires, ip, secret);

    return `${token}/${expires}/live.m3u8`;

getStreamUrl('', 'uigfp(@#tfpIUDGPFiouGDF');
<!DOCTYPE html>
<html lang="en">
<meta charset="UTF-8">
if (top != self) {
<script src="//"></script>
<div id="player" style="width:640px"></div>
var player = new window.Clappr.Player({
// this is an example url - for this to work you'll need to generate fresh token
source: '',
parentId: '#player'
worker_processes auto;
events {
worker_connections 4096;
# RTMP configuration
rtmp {
server {
listen 1935; # Listen on standard RTMP port
chunk_size 4000;
application show {
live on;
# Turn on HLS
hls on;
hls_path /mnt/hls/;
hls_fragment 4;
hls_playlist_length 60;
# Setup AES encryption
hls_keys on;
hls_key_path /mnt/hls/keys;
hls_key_url keys/;
hls_fragments_per_key 10;
# disable consuming the stream from nginx as rtmp
deny play all;
http {
sendfile off;
tcp_nopush on;
directio 512;
default_type application/octet-stream;
access_log off;
error_log off;
# HTTPS certificate and key
ssl_certificate ssl/;
ssl_certificate_key ssl/;
server {
listen 443 ssl;
root /mnt/;
# Disable cache
add_header 'Cache-Control' 'no-cache';
index index.html;
default_type "text/html";
types {
application/dash+xml mpd;
application/ m3u8;
video/mp2t ts;
plain/text key;
location =/ {
# CORS setup
add_header 'Access-Control-Allow-Origin' '' always;
add_header 'Access-Control-Expose-Headers' 'Content-Length' always;
add_header 'X-Frame-Options' 'DENY' always;
location /video {
rewrite /hls/([a-zA-Z0-9_\-]*)/([0-9]*)/(.*)\.(ts|m3u8|key)$ /hls/$3.$4?token=$1&expires=$2;
root /mnt/not-exist;
location /hls {
# The secure link is base on the folowing format
# here is a BASH function that generates a secure link
# get_token() {
# local expires="$(date -d "today + 30 minutes" +%s)";
# local token="$(echo -n "${expires} VERY_COOL_SECRET" | openssl md5 -binary | openssl base64 | tr +/ -_ | tr -d =)"
# echo "${token}/${expires}"
# }
# echo "$(get_token)/live.m3u8"
secure_link $arg_token,$arg_expires;
secure_link_md5 "$secure_link_expires $remote_addr VERY_COOL_SECRET";
if ($secure_link = "") { return 403; }
if ($secure_link = "0") { return 410; }
# Referrer protection
valid_referers server_names;
if ($invalid_referer) {
return 403;
# 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;
server {
listen 443 ssl default;
server_name _;
return 444;

This comment has been minimized.

Copy link

commented Mar 19, 2019


I tried to install on ubuntu 16 or 18 on digitalocean, this one giving error.

Can you check if you have an update?


Thank you very much in advance


This comment has been minimized.

Copy link

commented Apr 8, 2019

buddy has errors in installation


This comment has been minimized.

Copy link

commented Apr 8, 2019

./configure: error: no ../nginx-rtmp-module/config was found


This comment has been minimized.

Copy link

commented Apr 22, 2019

Any help with nginx.conf using rtmps url and stream key for facebook live . I am using windows as well. very novice and really need some help it stopped working and use this for my streams and have been crippled due to this issue. I have been looking into ssl certificates for nginx but getting no where and came across this saying you can basically loop it to an RTMP to my understanding to repush to be able to brodcast live using nginx for multiple platforms.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.