Skip to content

Instantly share code, notes, and snippets.

@mrbar42 mrbar42/
Last active Feb 18, 2020

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

comerciosite 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

inat-tv commented Apr 8, 2019

buddy has errors in installation


This comment has been minimized.

Copy link

inat-tv commented Apr 8, 2019

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


This comment has been minimized.

Copy link

ajhansen91 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.


This comment has been minimized.

Copy link

clovrd commented Oct 25, 2019

Works like charm, thx. There's a little err in the node example though. It has to be
var input = expires + ' ' + client_ip + ' ' + secret;
otherwise the token will not be correct.



This comment has been minimized.

Copy link

elmadkoun commented Dec 17, 2019

Thanks for the explanation
How do I run bash in order to create token pleas


This comment has been minimized.

Copy link

luismanolo commented Dec 18, 2019

Good work.

Many thanks.

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.