Reverse Proxy with Nginx and Cloudflare

Why Reverse Proxy?

Initially, my reason for setting up a reverse proxy with Nginx was to force SSL connections to my Plex Media Server and to eliminate the need to use a non-standard port.  The goal was to make it so all connections came through HTTPS protocols with a trusted SSL certificate.  To achieve this, I chose to set up Nginx on my vMediaserv virtual machine which hosts the Plex server.

Setting Up Nginx

Setting up Nginx on a Windows VM is far from easy.  Nginx is best suited for Linux hosts.   I decided not to create a dedicated Linux box for this and charged forward with my plan to use it on the Windows VM hosting Plex.

Getting Nginx running and web-accessible was the easy part; all I had to do was install it and set up port forwarding for HTTP and HTTPS protocols.   I also set up HTTP ‘server’ nodes to force HTTPS using 301 redirects.

server {
	listen 80;
	listen [::]:80;
	return 301 https://$server_name$request_uri;

However, the real challenge came when I needed to appropriately configure the various ‘server’ nodes for the multiple sub-domains I needed.  The most difficult ‘server’ to configure was by far the Plex server.  It required more advanced data handling.  Here’s a sanitized snippet of most of the SSL management and data handling:

ssl_protocols TLSv1 TLSv1.1 TLSv1.2; 
ssl_prefer_server_ciphers on; 
ssl_ciphers "***************************************"; 
ssl_ecdh_curve secp384r1; 

ssl_session_cache shared:SSL:10m; 
ssl_session_timeout 10m;
#Reuse ssl sessions, avoids unnecessary handshakes
#Turning this on will increase performance, but at the cost of security. Read below before making a choice.
ssl_session_tickets off; 
#Why this is important:
ssl_stapling on; 
ssl_stapling_verify on; 
#Faster resolving, improves stapling time. Timeout and nameservers may need to be adjusted for your location Google's have been used here.
resolver valid=300s; 
resolver_timeout 10s;

add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload"; 
#add_header X-Frame-Options DENY; 
add_header X-Content-Type-Options nosniff;
##LetsEncrypt certificates
ssl_certificate      C:/****/cert.pem;
ssl_certificate_key  C:/****/key.pem;
ssl_client_certificate C:/****/cloudflare.crt;
ssl_verify_client on;
#Plex has A LOT of javascript, xml and html. 
#This helps a lot, but if it causes playback issues with devices turn it off.
gzip on;
gzip_vary on;
gzip_min_length 1000; index index.html index.htm index.php;
gzip_proxied any;
gzip_types text/plain text/css text/xml application/xml text/javascript application/x-javascript image/svg+xml;
gzip_disable "MSIE [1-6]\.";
#Forward real ip and host to Plex
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
#When using ngx_http_realip_module change $proxy_add_x_forwarded_for to '$http_x_forwarded_for,$realip_remote_addr'
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;

proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";

#Buffering off send to the client as soon as the data is received from Plex.
proxy_redirect off;
proxy_buffering off;

I was finally able to access Plex using addressing.  It even forces all browsers to use HTTPS with my 301 redirects! The Plex node I created also serves access to Plex auxiliary apps like Tautulli and Ombi.  Other nodes with different subdomains were created for things like Minecraft and its plugins (e.g. Dynmap).

Creating SSL certificates with LetsEncrypt proved to be quite a challenge since I was using a Windows host. I was ultimately able to achieve it though by using a an app I found on github called Win-Acme.

Adding Cloudflare

Another issue I had discovered while hosting my Plex Media Server was that some remote users were having buffering problems on videos streamed at rates higher than 2Mbps.  This only seemed to affect users of a specific ISP.  Upon some extensive research, I determined the most likely culprit was poor peering between my ISP and the end-user’s ISP.  The best fix for this was to implement a CDN like Cloudflare.

By routing all my Plex server traffic through Cloudflare, I could drastically improve the peering since traffic is no longer going directly from one residential ISP to another residential ISP.  Setting up Cloudflare itself was easy;  I just imported my DNS zones and redirected my domain’s name servers to Cloudflare.   From there,  I activated the Cloudflare routing for the Plex server’s subdomain.  However, I also needed to update the way SSL worked for Plex in the Nginx node.  This was accomplished by adapting the instructions from this article.

After adding my Origin CA and the generated Key/Chain, it took about 24 hours for everything to fully propagate.  Now Cloudflare handles all traffic to my Plex subdomain.  I can now utilize my full upload speeds of 20Mbps to properly stream up to 4k to the users who first reported issues.


It was a long project and I felt like I learned a lot about SSL certificates and network peering.  Ever since the project was completed, users have reported smooth operation, even on the more demanding videos.  I’m sure this won’t be the last time I work on my Plex infrastructure, but I’m really glad I chose to work on this.  Maybe next time I’ll try Docker instances or Linux for my web applications!