Jump to content
Swynol

HOW TO: NGINX Reverse Proxy

Recommended Posts

Swynol

There have been a few posts around the Forum recently regarding SSL, HTTPS and Security.

 

I'm by no means an expert on reverse proxies but have had alot of dealings with them over the past few months and with the help of @@pir8radio and @@shorty1483 have a fairly well setup and secure system to access my services from outside of my LAN. This guide is to help people access their Emby Server and any other services behind a reverse proxy. This is based on NGINX but it also works for Apache and IIS.

 

So firstly, what is and why do i need a reverse proxy?

If you’re like me and have many services running on servers or PCs in your home, i.e. Emby, Plex, Sonarr, Radarr, Ombi, Organizer, CP, home automation, CCTV and anything else. Then you have to open multiple ports on your router to direct traffic to where it needs to go. With a Reverse Proxy you only have to open 1 or 2 ports. Normally all HTTP traffic is sent over port 80 and HTTPS traffic over port 443. In my case I want all traffic served over HTTPS and port 443 so I close all ports bar 443.

 

Another reason to use a reverse proxy is that you can use your own domain certs easily and fine tune your security settings. If you want to test your Domain security go here - https://securityheaders.io/ Chances are your rating will be an F. with reverse proxy you can easily attain a B+/A Grade.

 

You can also setup a web faced server running NGINX and then have additional servers behind that hidden on your LAN, however if your like me I have NGINX running on the same machine as emby.

 

I only access Emby remotely do i still need a reverse proxy?

Difficult to answer. No you dont need a reverse proxy to access Emby, but if you do then you can fine tune the security.

 

This guide assumes you have a Domain name, your own Certs to go with your domain name and either have your domain name pointed to a static PC (your home WAN IP) or have Dynamic DNS setup.

 

Have I convinced you yet?

 

I run Windows OS at home so this guide follows a Windows setup but the config will be the same across all OS.

 

1.       Download the latest version of NGINX from here - http://nginx-win.ecsds.eu/ as of writing this guide its version 1.13.0.1 Violet.

2.       Extract the ZIP file somewhere easy to find. C:\NGINX. 

a.       To make future updating easier when you extract the ZIP the file is called nginx 1.13.0.1 Violet. Rename it to just NGINX.

3.       Before we get started on the config of NGINX lets install it as a service.

a.       Download NSSM

b.      Extract the ZIP

c.       Copy correct x86 or x64 nssm.exe to C:\Windows\System32

d.      Open a CMD, type ‘nssm install nginx’

e.      Fill in the Application Path – C:\NGINX\nginx.exe

Startup directory – C:\NGINX

Service name – NGINX.

Install Service

 

Don’t Start the service yet, we need to configure NGINX.

To create a config I use notepad++. I will go through each setting first before supplying a copy of my current config. 

 

This is how the config starts.

worker_processes  2;

events {
    worker_connections  8192;
}

http {
    include       mime.types;
    default_type  application/octet-stream;
    server_tokens off;

    sendfile        off;

gzip on;
gzip_disable "msie6";

gzip_comp_level 6;
gzip_min_length 1100;
gzip_buffers 16 8k;
gzip_proxied any;
gzip_types
text/plain
text/css
text/js
text/xml
text/javascript
application/javascript
application/x-javascript
application/json
application/xml
application/rss+xml
image/svg+xml;

tcp_nodelay on;

    server_names_hash_bucket_size 128;
    map_hash_bucket_size 64;

## Start: Timeouts ##
    client_body_timeout   10;
    client_header_timeout 10;
    keepalive_timeout     30;
    send_timeout          10;
    keepalive_requests    10;
## End: Timeouts ##


}

This part is fairly standard. anything starting with # is disabled or just a comment. The config is broken down into blocks. the first block here is the HTTP block. The HTTP block contains all the headers required to do the work of the reverse proxy for example when someone browses to emby.mydomain.com it matches a header in NGINX and it knows where to forward the data.

 

The only change in the section above over a default config is the addition of server_tokens off;    this is the first of our security tweaks. This removes the version of NGINX from being visible outside your network and less chances of attackers being able to exploit version weaknesses. 

## Default Listening ##

server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name _;
	  return 301 https://$host$request_uri;
}	

This next block is called a server block and it nested inside the HTTP block. This block is optional, it is only used to redirect any users from HTTP to HTTPS if you want to force users on HTTPS only.

 

listen 80 and listen [::] 80 are default ports for HTTP traffic for IPv4 and IPv6.

 

return 301 https://$host$request_uri;   is what rewrites the request from HTTP to HTTPS. Again only needed if you are forcing everyone to use HTTPS only.

##EMBY Server##
	
	server {
    listen 80;
    listen [::] 80;
    listen [::]:443 ssl;
    listen 443 ssl;
    server_name emby.mydomain.com; 
	
        ssl_session_timeout 30m;
        ssl_protocols TLSv1.2 TLSv1.1 TLSv1;
	ssl_certificate      SSL/cert.pem;
	ssl_certificate_key  SSL/private.key;
        ssl_session_cache shared:SSL:10m;
	
		#add_header Public-Key-Pins '
		#pin-sha256="8TzXdhbnv+l6EjDG2Vj9EmgGiSmZenrTZSNaUFEwyUE="; 
		#pin-sha256="YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg="; 
		#pin-sha256="Vjs8r4z+80wjNcr1YKepWQboSIRi63WsWXhIMN+eWys="; 
		#max-age=86400; includeSubDomains';
		
		add_header X-Xss-Protection "1; mode=block" always;
		add_header X-Content-Type-Options "nosniff" always;
		add_header Strict-Transport-Security "max-age=2592000; includeSubdomains" always;
		add_header X-Frame-Options "SAMEORIGIN" always;
		proxy_hide_header X-Powered-By;
		add_header 'Referrer-Policy' 'no-referrer';
		add_header Content-Security-Policy "frame-ancestors mydomain.com emby.mydomain.com;";
    	
	
     location / {
        proxy_pass http://192.168.10.10:8096;  

		proxy_set_header Range $http_range;
		proxy_set_header If-Range $http_if_range;
		proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

        #Next three lines allow websockets
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
		}
	

}

The next server block is where the magic happens. 

 

First the listen 80; and listen [::] 80; are only needed if you want to allow users to access your emby server on port 80. otherwise delete these 2 lines to force all users to HTTPS access.

Listen 443 ssl; and listen [::] 443 ssl; are the default HTTPS ports again for IPv4 and IPv6.

server_name emby.mydomain.com will be your subdomain and how you access emby from outside your network.

 

Now lets look at the SSL certificates, for my setup I created a .pem file. this file contains both my cert, intermediate and CA root cert in one file. This link gives you an idea how to do it - https://www.digicert.com/ssl-support/pem-ssl-creation.htm 

 

you should now have your cert.pem and a private.key file. for simplicity copy these files to C:\NGINX\conf\SSL  (you have to create the SSL folder) This tells NGINX where to find the certs.

ssl_certificate SSL/cert.pem;

ssl_certificate_key SSL/private.key;

 

For now I am going to skip over the #add_header Public-Key-Pins - as you can see i have it disabled by using # in front of it. I will explain why later on.

 

The next section adds further security tweaks, you will need to change the content-security-policy domain names to your own. you need to list all your subdomains i.e. sonarr.mydomain.com radarr.mydomain.com emby.my....... you get the idea.

 

        add_header X-Xss-Protection "1; mode=block" always;
        add_header X-Content-Type-Options "nosniff" always;
        add_header Strict-Transport-Security "max-age=2592000; includeSubdomains" always;
        add_header X-Frame-Options "SAMEORIGIN" always;
        proxy_hide_header X-Powered-By;
        add_header 'Referrer-Policy' 'no-referrer';

        add_header Content-Security-Policy "frame-ancestors mydomain.com emby.mydomain.com;";

 

 

The next part is called the location block. This is what tells your domain name emby.mydomain.com where the data should go. In this case it forwards everything to proxy_pass http://192.168.10.10:8096  you can also forward to proxy_pass http://127.0.0.1:8096  if it runs on the same box as NGINX. the rest of the location block is default stuff to help the data get to where it is needed.

 

 

 

 

 

Your Config should now look like the one below. we need to save it to C:\NGINX\conf   and name it nginx.conf

worker_processes  2;

events {
    worker_connections  8192;
}

http {
    include       mime.types;
    default_type  application/octet-stream;
    server_tokens off;

    sendfile        off;

    server_names_hash_bucket_size 128;
    map_hash_bucket_size 64;

## Start: Timeouts ##
    client_body_timeout   10;
    client_header_timeout 10;
    keepalive_timeout     30;
    send_timeout          10;
    keepalive_requests    10;
## End: Timeouts ##

## Default Listening ##

server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
     return 301 https://$host$request_uri;
}    

##EMBY Server##
    
    server {
listen [::]:443 ssl;
listen 443 ssl;
server_name emby.mydomain.com; 
    
ssl_session_timeout 30m;
ssl_protocols TLSv1.2 TLSv1.1 TLSv1;
    ssl_certificate SSL/cert.pem;
    ssl_certificate_key SSL/private.key;
ssl_session_cache shared:SSL:10m;
    
        #add_header Public-Key-Pins '
        #pin-sha256="8TzXdhbnv+l6EjDG2Vj9EmgGiSmZenrTZSNUFEwyUE=";
        #pin-sha256="YLh1dUR9y6Kja30RrAn7JKnbQG/utLMkBgFF2Fuihg=";
        #pin-sha256="Vjs8r4z+80wjNcr1KepWQboSIRi63WsWXhIMN+eWys=";
        #max-age=86400; includeSubDomains';
        
        add_header X-Xss-Protection "1; mode=block" always;
        add_header X-Content-Type-Options "nosniff" always;
        add_header Strict-Transport-Security "max-age=2592000; includeSubdomains" always;
        add_header X-Frame-Options "SAMEORIGIN" always;
        proxy_hide_header X-Powered-By;
        add_header 'Referrer-Policy' 'no-referrer';
        add_header Content-Security-Policy "frame-ancestors mydomain.com emby.mydomain.com;";
    
    
location / {
proxy_pass http://192.168.10.10:8096; 

        proxy_set_header Range $http_range;
        proxy_set_header If-Range $http_if_range;
        proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

#Next three lines allow websockets
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
        }
    
}
}

And thats it, you can now start your NGINX services by running services.msc and starting NGINX.

Edited by Swynol
  • Like 9

Share this post


Link to post
Share on other sites
Swynol

With that above you should now have a secure access to your emby server. it will get a B+/A Grade on most website security tests.

 

So now you have caught the bug and you want to get the elusive A+ grade, how do you do it?

 

You need to use something called HPKP or private-key-pinning. This comes with a warning and I highly recommend doing alot of research on it before you decide to enable it. The main risk is if you change your cert, or it expires or you lose your hashes then you can temporarily or permanent lose access to your websites/webapps. 

 

To get your pin hashes you need to have NGINX running with your certificates.

 

go to https://report-uri.io/home/pkp_hash and enter any of your subdomains. it will extract the Hashes, you should have 3 hashes, one for your cert, one for intermediate and one for CA Root.

 

The idea is that this blocks man in the middle attacks and prevents rogue certificates like what happened fairly recently with StartComSSL. they were taken over by another company and started issuing dodgy certs.

 

So why 3 hashes. ok so to access your website any one of the 3 hashes has to be vaild. If you change your domain cert then a hash will change so the first hash will become invaild. If you only had this one Hash then anyone who has already visited your site will have the old Hash cached, and because it doesnt match they will be refused access to the requested site. The time of the cache depends on the max-age= (most security sites look for a max-age of 30-60 days minimum). There was a tech blog site which used a max-age of 1 year, when they changed their cert it started throwing key-pinning errors to users who had previously visited their site and with a max-age of 1 year they would have to wait 1 year before they gained access again.

 

This is why we have hashes for both intermediate and CA root, these are less likely to change or expire so offers a backup way to access your site if your own cert changes. you could even add additional Hashes from other CA roots etc.

 

 #add_header Public-Key-Pins '

 #pin-sha256="8TzXdhbnv+l6EjDG2Vj9EmgGSmZenrTZSNUFEwyUE=";
 #pin-sha256="YLh1dUR9y6Kj30RrAn7JKnbQG/utLMkBgFF2Fuihg=";
 #pin-sha256="Vjs8r4z+80wjNcr1KepWQboSIRi63WsXhIMN+eWys=";
 #max-age=86400; includeSubDomains';

 

In conclusion you could break your sites by using key-pinning. it does offer some security protection but you will have to decide on whether it is worth the risk. To negate the risk you could always lower the max-age   to something much lower like 1 hour, 1 day. It doesnt offer the same level of security but its probably an improvement of not using it at all and the risk is lower. Most security tests will see that your using key-pinning but warn you that the max-age is too short.

  • Like 2

Share this post


Link to post
Share on other sites
Swynol

added a section to the HTTP block as recommended by @@pir8radio

This has given my services behind the reverse proxy a speed boost.

 gzip on;
gzip_disable "msie6";

gzip_comp_level 6;
gzip_min_length 1100;
gzip_buffers 16 8k;
gzip_proxied any;
gzip_types
   text/plain
   text/css
   text/js
   text/xml
   text/javascript
   application/javascript
   application/x-javascript
   application/json
   application/xml
   application/rss+xml
   image/svg+xml;

    tcp_nodelay on;
  • Like 1

Share this post


Link to post
Share on other sites
Swynol

This is my latest NGINX config. added a few more security options. Most SSL testing websites should now give you a A or A- rating.

worker_processes  2;


events {
    worker_connections  8192;
}


http {
    include       mime.types;
    default_type  application/octet-stream;
	server_tokens off;
	
	gzip on;
	gzip_disable "msie6";

	gzip_comp_level 6;
	gzip_min_length 1100;
	gzip_buffers 16 8k;
	gzip_proxied any;
	gzip_types
    text/plain
    text/css
    text/js
    text/xml
    text/javascript
    application/javascript
    application/x-javascript
    application/json
    application/xml
    application/rss+xml
    image/svg+xml;

    tcp_nodelay on;

    sendfile        off;

    server_names_hash_bucket_size 128;
    map_hash_bucket_size 64;

## Start: Timeouts ##
    client_body_timeout   10;
    client_header_timeout 10;
    keepalive_timeout     30;
    send_timeout          10;
    keepalive_requests    10;
## End: Timeouts ##

	
	
## Default Listening ##

server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name _;
	  
	  return 301 https://$host$request_uri;
}	

##EMBY Server##

server {
listen [::]:80;
listen 80;
listen [::]:443 ssl;
listen 443 ssl;
server_name https://emby.mydomain.media;   #your subdomain.domainname.com here

ssl_session_timeout 30m;
ssl_protocols TLSv1.2 TLSv1.1 TLSv1;
ssl_certificate      SSL/cert.pem;
ssl_certificate_key  SSL/private.key;
ssl_session_cache shared:SSL:10m;
ssl_prefer_server_ciphers on;
ssl_ciphers ECDH+AESGCM:ECDH+AES256:ECDH+AES128:DH+3DES:!ADH:!AECDH:!MD5;
		
proxy_hide_header X-Powered-By;
add_header X-Xss-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff"  always;
add_header Strict-Transport-Security "max-age=2592000; includeSubdomains" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header 'Referrer-Policy' 'no-referrer';

add_header Content-Security-Policy "frame-ancestors mydomain.com emby.mydomain.com;";   #add your domainname and all subdomains listed on your cert
		

location / {
proxy_pass http://127.0.0.1:8096; # Local emby ip and non SSL port

proxy_hide_header X-Powered-By;
proxy_set_header Range $http_range;
proxy_set_header If-Range $http_if_range;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

#Next three lines allow websockets
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}


}
  • Like 4

Share this post


Link to post
Share on other sites
Guest
This topic is now closed to further replies.

×
×
  • Create New...