Plex and the Privacy VPN Conundrum ; nginx to the rescue!

As an advocate in Internet Privacy I use a VPN configured on my router to obfuscate my browsing habits. My one bugbear with this solution has always been that Plex doesn’t work (well / if at all) in this scenario, and even when it does it’s slower due to the additional encryption / hops the VPN adds to the mix.

I’ve read countless “solutions” to this, for me, this is one of the easier – albeit you need to have either a VM to hand or a spare low-power x86 device and your own custom domain.

I recently started using nginx to proxy guacamole, on a dedicated, VPN-bypassed client. This got me thinking… could I proxy my remote Plex server (that was behind the VPN) via a dedicated VM running nginx? The answer, of course, was yes.

The example deployment / configuration below was completed on a Debian 8.6 Jessie install however, once you have nginx installed the configuration steps will be the same.

I’ve tested this solution with the Android Plex App, as well as the Plex Web App – no issues found to date!

First, lets install nginx and configure it to run on boot:

apt-get install -y curl

touch /etc/apt/sources.list.d/nginx.list

echo 'deb http://nginx.org/packages/debian/ jessie nginx' >> /etc/apt/sources.list.d/nginx.list

echo 'deb-src http://nginx.org/packages/debian/ jessie nginx' >> /etc/apt/sources.list.d/nginx.list

curl http://nginx.org/keys/nginx_signing.key | apt-key add -

apt-get update
apt-get install -y nginx

/etc/init.d/nginx start
systemctl enable nginx

We will harden the nginx server as we progress through the configuration. Firstly, lets block malicious agents – this is uniform across both HTTP and HTTPS configurations:

vi /etc/nginx/blockuseragents.rules

Contents of this file below:

map $http_user_agent $blockedagent {
default 0;
~*malicious 1;
~*bot 1;
~*backdoor 1;
~*crawler 1;
~*bandit 1;
}

SSL Configuration (needs an SSL certificate, try StartSSL – it’s free!)

Honestly – there is no good reason not to deploy an HTTPS-enabled reverse proxy. StartSSL certificates are free for personal use. Without this you’d be sending your Plex account username and password across the internet unencrypted.

*** Using these instructions, you do not need to add these certificates to plex itself.***

First, let’s address weak Diffie-Hellman (DH) key exchange parameters:

cd /etc/nginx/ssl
# This will take *an age* to complete
openssl dhparam -out dhparams.pem 4096

Now we’ll create the nginx .conf file for your server as below, be sure to change the relevant servername.

vi /etc/nginx/conf.d/domain.com.conf

Contents as below – be sure to review relevant SSL certificate files required in order to start nginx, these will vary by third-party CA.

include /etc/nginx/blockuseragents.rules;

# Hardening as-per https://gist.github.com/plentz/6737338
server_tokens off;
add_header X-Frame-Options SAMEORIGIN;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header Strict-Transport-Security "max-age=31622400; includeSubDomains; preload";

# Define upstream Plex server location - change this for your env
upstream plex-upstream {
 # change plex-server.example.com:32400 to the hostname:port of your plex server.
 # this can be "localhost:32400", for instance, if Plex is running on the same server as nginx.
 server 192.168.1.250:32400;
}

# Redirect http traffic for plex.domain.com to https - LAN only
server {
 if ($blockedagent) {
 return 403;
 }
 if ($request_method !~ ^(GET|HEAD|POST)$) {
 return 444;
 }
 listen 80;
 server_name plex.domain.com;
 return 301 https://$server_name$request_uri;
}

# Plex Reverse Proxy HTTPS server
server {
 if ($blockedagent) {
 return 403;
 }
 if ($request_method !~ ^(GET|HEAD|POST|PUT)$) {
 return 444;
 }
 listen 443 ssl;
 server_name
 tv
 plex
 plex.domain.com;

 ssl_dhparam /etc/nginx/ssl/dhparams.pem;
 ssl_certificate /etc/nginx/ssl/1_www.plex.domain.com_bundle.crt;
 ssl_certificate_key /etc/nginx/ssl/plex_ssl.key;
 ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
 ssl_prefer_server_ciphers on;
 ssl_ciphers "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EECDH EDH+aRSA RC4 !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS !RC4";

 # Hardening as-per https://gist.github.com/plentz/6737338
 ssl_session_cache shared:SSL:50m;
 ssl_session_timeout 5m;
 resolver 8.8.8.8;
 ssl_stapling on;
 ssl_trusted_certificate /etc/nginx/ssl/1_plex.domain.com_bundle.crt;

 # As-per https://forums.plex.tv/discussion/224138/proper-reverse-proxy-for-nginx
 large_client_header_buffers 4 8k;

 # set some headers and proxy stuff.
 proxy_set_header Host $http_host;
 proxy_set_header X-Real-IP $remote_addr;
 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
 proxy_set_header X-Forwarded-Proto $scheme;
 proxy_redirect off;
 proxy_buffering off;

 location /:/websockets/notifications {
 # if a request to / comes in, 301 redirect to the main plex page.
 # but only if it doesn't contain the X-Plex-Device-Name header
 # this fixes a bug where you get permission issues when accessing the web dashboard
 if ($http_x_plex_device_name = '') {
 rewrite ^/$ https://$http_host/web/index.html;
 }

 # As-per https://forums.plex.tv/discussion/224138/proper-reverse-proxy-for-nginx
 proxy_http_version 1.1;
 proxy_set_header Upgrade $http_upgrade;
 proxy_set_header Connection "upgrade";
 proxy_read_timeout 86400;

 # proxy request to plex server
 proxy_pass http://plex-upstream;
 }
 location / {
 proxy_pass http://plex-upstream;
 }
}

Install, configure and enable ufw:

# Install Uncomplicated Firewall
apt-get install ufw

# Allow HTTPS from anywhere
ufw allow https
# Allow HTTP from anywhere - change LAN IP address range and server IP
ufw allow from 192.168.1.0/24 to 192.168.1.248 port 80
# Allow HTTPS
ufw allow https

For added security you can rate-limit new connections on port 443 – this *may* help reduce the chances of a brute force attack against your plex login page:

# Add just above the COMMIT line in this file /etc/ufw/before.rules
# Rate-limiting for HTTPS connections
vi /etc/ufw/before.rules
-A ufw-before-input -p tcp --dport 443 -i eth0 -m state --state NEW -m recent --set
-A ufw-before-input -p tcp --dport 443 -i eth0 -m state --state NEW -m recent --update --seconds 2 --hitcount 20 -j DROP

You’ll then need to do one of two things:

  1. Install a dynamic dns client on your linux box and configure an appropriate A record
  2. Use dynamicdns on your router and configure an appropriate A record

When done, create a CNAME entry in your personal domain name that points to the dynamic DNS A record. I.e. if you used the config file above, and had a dynamic DNS A record of “myrouter.dyndns.com” and your personal domain is “domain.com” then your CNAME would be:

plex.domain.com pointing to myrouter.dyndns.com

Test your SSL config using the following free service: https://www.ssllabs.com/ssltest/

Finally, within Plex, under Settings | Server | Network (Show Advanced) ensure you populate the “Custom server access URLs” with (using URL from example config above – change as-per your environment/ needs):

https://plex.domain.com