Setup a Nginx RTMP live-stream server & HLS video-player on a WordPress site on Ubuntu/Debian

PRE-PREPARATION

This guide was written up by André, that would be me, to accompany a new video on my Youtube channel. The video will be uploaded somewhere during the next two days so make sure you keep an eye on that!! In the video I follow my own written guide, make some adjustments here and there. I posted the end-results below the video here.

YouTube player

All the commands are also available on Pastebin (right here)
Get WinSCP here, and Putty here.

1. PREPARATION

Before you start you should make sure your server is fully updated.

sudo apt update
sudo apt upgrade

This step is optional, and actually NOT advised. Security-wise it would be better NOT to follow this next step. This command will enable the root account and this will allow us to set up our server without having to type sudo before every command and without having to type our password a gazillion times.

sudo su
passwd
[enter new password twice]

If you do decide to enable the root account like I just did, remember to disable it again once you’re done. Keep in mind that if you did not enable the root account you will need to enter “sudo” before most of the commands on this page.

For this next part it is very important that you know what a fully qualified hostname truly means. Read up first! We are going to set the hostname foir our server. If you want to follow the rest of this guide it is required to use a fully qualified domain name. Make sure you know exactly what your domain name / hostname is, that can be resolved to your IP, and has your web server respond to requests on port 80m and later on als 443. If you use a router with NAT you must make sure to open/forward these ports to your server.

sudo hostnamectl set-hostname YOUR.DOMAIN.COM

2. INSTALL THESE USEFUL / REQUIRED PACKAGES

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 unzip

3. INSTALL NGINX + RTMP MODULE

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

4. INSTALLING PHP & EDIT PHP.INI

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

The next couple of lines will search for a certain string in our php.ini fil and replace it with another.

sed -i 's/;cgi.fix_pathinfo=1/cgi.fix_pathinfo=0/g' /etc/php/7.3/fpm/php.ini
sed -i 's/upload_max_filesize = 2M/upload_max_filesize = 1024M/g' /etc/php/7.3/fpm/php.ini
sed -i 's/max_input_time = 60/max_input_time = 300/g' /etc/php/7.3/fpm/php.ini
sed -i 's/max_execution_time = 30/max_execution_time = 60/g' /etc/php/7.3/fpm/php.ini

Make sure you change the location to what suits your for you. Leave all the symbols like the as they are.

sed -i 's/;date.timezone =/date.timezone = "Europe/Amsterdam"/g' /etc/php/7.3/fpm/php.ini

You can of course also edit your php.ini with nano and make the changes you see above manually if you prefer.

nano /etc/php/7.3/fpm/php.ini

It won’t hurt to restart the php-fpm process at this time.

systemctl restart php7.3-fpm

5. INSTALL MARIADB / MYSQL AND CREATE WORDPRESS DATABASE

apt install mariadb-server mariadb-client phpmyadmin

When asked to choose between Apache and Lighttpd choose NONE. After install is complete, enter this command:

mysql_secure_installation

Answer all the questions as you prefer.

systemctl restart mysql
mysql -u root -p
[enter the password you set earlier]

We need to create a new database for our WordPress installation. Also we’ll create a new user that has access only to the Worpress database. Be sure to change the “YourPassword” to a different password.

CREATE DATABASE wordpress;
grant all privileges on wordpress.* TO 'wordpress'@'localhost' identified by 'YourPassword';
FLUSH PRIVILEGES;

These next steps are both optional. Please continue reading to see if these steps are for you or not.

This step will create a new root account that has access from remote locations. Be aware that this is not required, it’s your personal preference. This is only required if you want to manage your databases from a different computer using HeidiSQL for example. Keep in mind that it is a lot more secure NOT to do this, so skip ahead to the next step if you don’t need this functionality. When you’re not sure or in doubt, just skip to the next step also.

CREATE USER 'root'@'%' IDENTIFIED BY 'YourPassword';
GRANT ALL ON . TO 'root'@'%';
FLUSH PRIVILEGES;

Use the following commands to create an admin user for Mariadb. Be sure to change “username” and “YourPassword” to whatever you like.

CREATE USER 'username'@'%' IDENTIFIED BY 'YourPassword';
GRANT ALL ON . TO 'username'@'%';
GRANT ALL PRIVILEGES ON . TO 'username'@'%';
FLUSH PRIVILEGES;

This step is also optional. It might be required to use these commands if you want to be able to login with the root account from PHPMyAdmin. Only do this if you are certain you want to allow root login from PHPMyAdmin.

use mysql;
update user set plugin='' where User='root';
FLUSH PRIVILEGES;
quit;
systemctl restart mysql

Please be aware that you change “yourhostname” in the commands below with your fully qualified hostname like for example: host.domain.com

mkdir -p /var/www/yourhostname
ln -s /usr/share/phpmyadmin /var/www/yourhostname/phpmyadmin
chown -R www-data: /var/www/yourhostname

You can now test if it works by opening it. http://yourhostname/phpmyadmin

6. CLONE THE RTMP GIT

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

Copy/paste the next five lines in our new file.

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

Or use https://pastebin.com/ZK40Yyix

cp /var/www/html/crossdomain.xml /var/www/yourhostname/crossdomain.xml
nano /var/www/yourhostname/phpinfo.php
<?php phpinfo(); ?>

Or use: https://pastebin.com/JdkmJBrT

chown -R www-data:www-data /var/www/yourhostname
chown -R www-data:www-data /var/www/html

7. EDIT NGINX CONFIGURATION

For now we’ll create a config without https. This comes later. It is very important to change all the lines that contain “yourhostname” or “yourdomain” in to your fully qualified, working hostname if you want to create valid certificates later on.

nano /etc/nginx/nginx.conf
user www-data;
worker_processes 1;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;

events {
	worker_connections 768;
	# multi_accept on;
	}

http {
	sendfile on;
	tcp_nopush on;
	tcp_nodelay on;
	keepalive_timeout 65;
	types_hash_max_size 2048;
	# server_tokens off;
	# server_names_hash_bucket_size 64;
	# server_name_in_redirect off;
	include /etc/nginx/mime.types;
	default_type application/octet-stream;

	gzip off;
	# gzip_vary on;
	# 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;

	access_log /var/log/nginx/access.log;
	error_log /var/log/nginx/error.log;

	include /etc/nginx/conf.d/*.conf;
	include /etc/nginx/sites-enabled/*;
	}

rtmp {
	access_log /var/log/nginx/rtmp_access.log;
	server {
		listen 1935;
                chunk_size 8192;
		
                application live {
                        live on;
			meta on;
                        record off;
			interleave off;
			wait_key on;
			wait_video off;
			idle_streams off;
			sync 300ms;
			session_relay on;
			max_connections 1000;
			allow publish all;
			allow play all;
			hls off;
			dash off;

			# on_publish http://yourdomain.com/plugin/Live/on_publish.php;
			# on_play http://yourdomain/plugin/Live/on_play.php;
			# on_record_done http://yourdomain/plugin/Live/on_record_done.php;

			push rtmp://localhost/hls;
			push rtmp://localhost/dash;

		}
		application hls {
			live on;
			record off;
			meta copy;
			allow publish 127.0.0.1;
			allow play all;

			hls on;
			hls_nested on;
			hls_cleanup on;
			hls_sync 100ms;
			hls_fragment 2s;
			hls_playlist_length 10s;
			hls_path /var/livestream/hls;
			}
		application dash {
			live on;
			record off;
			allow publish 127.0.0.1;
			deny publish all;
			allow play all;

			dash on;
			dash_nested on;
			dash_cleanup on;
			dash_fragment 5s;
			dash_playlist_length 20s;
			dash_path /var/livestream/dash;
			}
		application vods {
			play /var/livestream/recordings;
			allow play all;
			}
		application vods_http {
			play ;
			allow play all;
			}
		}
	}

Or use: https://pastebin.com/AKzEHMcd

8. CREATE THE REQUIRED FOLDERS

It’s ok if you see a message that the folder could no be created because it already exists.

mkdir /var/log/nginx
mkdir -p /var/livestream/hls
mkdir -p /var/livestream/dash
mkdir -p /var/livestream/recordings
chown -R www-data:www-data /var/log/nginx
chown -R www-data:www-data /var/livestream

9. EDIT THE WEBSITE’S CONFIG

Obviously the same thing as earlier is applicable here. Replace all the “yourhostname” entries with your own domain or hostname.

nano /etc/nginx/sites-available/yourhostname.conf
server {
	listen 80;
	listen [::]:80;

	server_name yourhostname;

	root /var/www/yourhostname;
	index index.php index.html index-nginx.html index.htm;

	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 ~ .php$ {
		include snippets/fastcgi-php.conf;
		fastcgi_pass unix:/var/run/php/php7.3-fpm.sock;
		# fastcgi_pass 127.0.0.1:9000;
		}
        location /stat {
		rtmp_stat all;
		rtmp_stat_stylesheet stat.xsl;
		#auth_basic Restricted Content;
		#auth_basic_user_file .htpasswd;
		}
        location /stat.xsl {
		root html;
		}
        location /control {
		rtmp_control all;
		#auth_basic stream;
		#auth_basic_user_file .htpasswd;
		}
	location ~ /.ht {
		deny all;
		}
        location /hls {
		types {
		application/vnd.apple.mpegurl m3u8;  
		video/mp2t ts;  
		}
		autoindex on;
		alias /var/livestream/hls;

		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 {
		types{  
		application/dash+xml mpd;
		video/mp4 mp4;
		} 
		autoindex on;
		alias /var/livestream/dash;

		add_header	Strict-Transport-Security "max-age=63072000";
		add_header Cache-Control no-cache;
		expires -1;
		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;
		}
	}
}

Or use: https://pastebin.com/gh2AnQ3Q

LET’S ACTIVATE THE DOMAIN AND SEE IF EVERYTHING WORKS

ln -s /etc/nginx/sites-available/yourhostname.conf /etc/nginx/sites-enabled/
nginx -t
systemctl restart nginx

10. LET’S CREATE CERTIFICATES

apt install python-certbot-nginx
certbot --nginx -d yourhostname

Enter your email address when asked, and choose YES to the option that certbot will create a forward for you from http to https.

Keep an eye on the results to see if it all went ok, or if you get any errors that will show what went wrong.

nginx -t
systemctl restart nginx
nano /etc/nginx/sites-available/yourhostname.conf

Make sure that the config file has lines that are like the ones below, and add them or edit the file accordingly.

listen 443 ssl http2;
listen [::]:443 ssl http2;
ssl_certificate /etc/letsencrypt/live/yourhostname/cert.pem;
ssl_certificate_key /etc/letsencrypt/live/yourhostname/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

Or use: https://pastebin.com/1ECAJbUa

As you may have noticed there’s a reference to another .pem file on the config. We still need to create that file.

By default, Nginx will use the default DHE (Ephemeral Diffie-Hellman) paramaters provided by openssl. This uses a weak key that gets lower scores. The best thing to do is build your own. You can create a 2048 bit key, but let’s go ahead and use 4096. First, you need to build the file.

openssl dhparam -out /etc/letsencrypt/ssl-dhparams.pem 4096

This may take a very long time. Don’t be surprised if this process will take more as 10 minutes. So you might as well make yourself a cup of coffee or something in the meantime.

The next line is probably not in your config file so add it manually if you want to get A+ ratings for your certificates. This is optional.

ssl_trusted_certificate /etc/letsencrypt/live/yourhostname/chain.pem;

Have a look at the config file that will be included by letsencrypt and change it where necessary

nano /etc/letsencrypt/options-ssl-nginx.conf
ssl_session_cache shared:le_nginx_SSL:1m;
ssl_session_timeout 1d;
ssl_session_tickets off;

ssl_protocols TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
ssl_ecdh_curve secp384r1;

ssl_stapling on;
ssl_stapling_verify on;

add_header Strict-Transport-Security "max-age=15768000; includeSubdomains; preload;";
add_header Referrer-Policy "no-referrer, strict-origin-when-cross-origin";
add_header X-Frame-Options SAMEORIGIN;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";

Or use: https://pastebin.com/amgEfNcQ

Go to ssllabs.com to test out your certificate. It should give you an A+ rating.
If it doesn’t, don’t worry about it. As long as everything is working, and you dont have a form of OCD like me, you can just skip the whole certificate rating part of these instructions.
https://www.ssllabs.com/ssltest/analyze.html?d=yourhostname

11. SETUP VIDEO.JS AND TEST STREAMING WITH OBS

If you’re following this guide just to setup live-streaming to wordpress and nothing else, then you can skip these next couple of steps.

We’ll be installing video.js to test if our live-stream is already working without using wordpress. This way it’s more easy to troubleshoot when it turns out it’not working.
Download and unzip the latest release of video.js and optionally some plugins. Place these in a sub folder for your website like /videojs

https://github.com/videojs/video.js/releases
https://github.com/videojs/http-streaming/releases

nano /var/www/yourhostname/livestreamhls.html

Go through the following lines carefully and adjust all the lines so it will work for your situation. Basically just edit all the lines that contain “yourhostname” and replace it with your hostname.

<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8 />
<title>Your Stream</title>
  <!--
 
  Uses the latest versions of video.js and videojs-http-streaming.
 
  To use specific versions, please change the URLs to the form:
 
  <link href="https://unpkg.com/video.js@6.7.1/dist/video-js.css" rel="stylesheet">
  <script src="https://unpkg.com/video.js@6.7.1/dist/video.js"></script>
  <script src="https://unpkg.com/@videojs/http-streaming@0.9.0/dist/videojs-http-streaming.js"></script>
 
  -->
  <link href="http://yourserver.ddns.net:8088/video.js/video-js.css" rel="stylesheet">
</head>
<body>
<center>
  <video-js id="live_stream" class="vjs-default-skin" controls autoplay preload="auto" width="1280" height="720">
    <source src="https://yourserver.ddns.net:8088/live/stream/index.m3u8" type="application/x-mpegURL">
  </video-js>
 
  <script src='https://yourserver.ddns.net:8088/video.js/video.js'></script>
  <script src="https://yourserver.ddns.net:8088/video.js/videojs-http-streaming.js"></script>
 
  <script>
    var player = videojs('live_stream');
  </script>
 </center>
</body>
</html>

Or use: https://pastebin.com/v7AJfUnB

To test if everything is working edit the options in your livestream app like OBS for example to use a custom server with the address:

rtmp://yourhostname/live 

With streamkey:

stream

Start streaming from OBS and have a look at the indexhls.html file we just created like so:

https://yourhostname/livestreamhls.html

If all went well you should see your live-stream appear within a minute. If it doesn’t work, I suggest you go through all the steps again and find out where it went wrong. Fix it before continuing to the next step.

11. SETTING UP WORDPRESS

Get the latest package file from the official WordPress site and unzip all the files from the wordpress folder inside the archive to your website’s root folder (in our case /var/www/yourhostname) and set the correct owner/group.

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

Now let’s start the WordPress installer. Go to your website address and you should see the first page of the installer there that starts with a menu to choose your language. Complete the installer and you have your WordPress site set up! Good job!

12. INSTALL HLS PLUGIN FOR WP

There are many different plugins out there that will provide you with a videoplayer that can show HLS streams. In this example I choose to use Video.js HLS Player. I advise you to try out different plugins to see which one most suits your needs. If you choose to use the same one as I just mentioned, all you have to do is add the following to a page or post once you’ve enabled the plugin.

[videojs_hls url="https://yourhostname/hls/stream/index.m3u8" width="1280" inline="true" autoplay="true"]

That actually completes this guide. I hope it was useful for you. I had fun making it because I know that many people will really appreciate it. That’s good enough for me! Keep an eye out for my future guides and video’s on Youtube! Hope to see you soon!

Here are all the links in random order that helped me set up this guide.

https://github.com/videojs/video.js/releases

https://github.com/videojs/http-streaming/releases

https://www.npmjs.com/package/videojs-contrib-dash

https://www.npmjs.com/package/videojs-playlist-ui

https://www.npmjs.com/package/videojs-seek-buttons

https://www.npmjs.com/package/videojs-logo

https://www.npmjs.com/package/@leochen1216/videojs-chromecast

https://www.npmjs.com/package/videojs-playlist

https://videojs.com/plugins