Skip to content

Instantly share code, notes, and snippets.

@jamesmacwhite
Forked from hazcod/apache-plex-reverse-proxy.vhost
Last active September 19, 2022 20:30
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jamesmacwhite/42b95c1f435eb16e9a7631358436d2f1 to your computer and use it in GitHub Desktop.
Save jamesmacwhite/42b95c1f435eb16e9a7631358436d2f1 to your computer and use it in GitHub Desktop.
Apache 2.4 reverse proxy VirtualHost configuration for Plex. Requires modules ssl, proxy, wstunnel

I no longer use Apache as a reverse proxy and moved to NGINX. No further updates will be made to this configuration. It may or may not work in the future, no warranty or support will be provided.

Apache 2.4 reverse proxy configuration for Plex Media Server

This VirtualHost configuration has been tested with the minimum requirements of Plex Media Server Version 1.16.5.1488 and Web Version: 3.108.2.

The TLS configuration only allows clients that support TLS 1.2+, this may cause issues for legacy clients. You may need to adjust this if specific clients have issues connecting. If however you are OK with having just TLS 1.2+ support, you can also enable the "Disable weak TLS versions" setting on your Plex server, given the reverse proxy will prevent a successful TLS negotiation for such clients anyway.

Requirements

  1. Apache 2.4.17 and above (for SSL/h2 related configuration)
  2. The following modules enabled: proxy, ssl, proxy_wstunnel, http, dir, env, headers, proxy_balancer, proxy_http, rewrite

Variables

This VirtualHost template uses four variables for easier configuration:

  • plex_url - IP/local DNS name of your Plex server in your LAN (Not the public domain)
  • plex_port - Local Plex Media Server port (usually 32400)
  • public_url - The public DNS name for reverse proxy access i.e. plex.yourdomain.com
  • email - ServerAdmin email address displayed on errors from Apache for the configured domain.

Why use a reverse proxy?

You may come across debates around configuring Plex with a reverse proxy. Technically you don't need to, Plex has it's own remote access feature which will forward port TCP 32400 externally through either uPnP (consider disabling uPnP for security) or through a custom port forward rule you've setup. A couple of advantages of a reverse proxy is being able to run Plex over SSL/TLS TCP 443 and taking advantage of SNI (Server Name Indication). Rather than forwarding loads of random services on different ports, you can have something like Nginx or Apache listen on TCP 80 and 443 and serve different domains/subdomains under a single IP address. Then using SNI, automatically return the correct site/service. In addition you could also get really creative and could also using something like HAproxy to serve different services based on the request, which can be more useful when you are dealing with more than just web based requests.

If you use Cloudflare for DNS, you can proxy your public Plex reverse proxy domain through it and benefit from better peering through their CDN, compared to directly connecting to your origin, which on average may have worse performance and Cloudflare could provide a better connection. This however may not always be true, so it may come with trade offs.

Note: You should prevent Plex traffic from being cached as this is potentially in violation of the Cloudflare ToS and could get your domain suspended.

It is worth highlighting that Plex is not directly designed with reverse proxy usage however, so support for it will be likely limited to community/other users. It is also possible, that a reverse proxy configuration can be broken at any time by a Plex update, which has happened before and may require tweaks or changes without warning.

Forcing secure connections

If you want to force secure connections, this reverse proxy setup by default will actually be broken by this because it is proxying plain HTTP. If you wanted to force secure connections everywhere, you need to update the existing ProxyPass and ProxyPassReverse lines to https://.

ProxyPass / https://${plex_url}:${plex_port}/
ProxyPassReverse / https://${plex_url}:${plex_port}/

However, without additional configuration, Apache will likely not be able to negotiate the TLS connection due to the default certificate from Plex not matching the public DNS name in your SSL certificate. You could configure a custom certificate on Plex to make this match however.

You'll likely see the following error from Apache (with debugging on) Cert does not match for name 'plex.example.com' You can however disable this check with:

SSLProxyCheckPeerName off

This will reduce your TLS security slightly, however you do not need to disable all SSL checks entirely such as SSLProxyVerify, as the default Plex certificate is valid and from a trusted CA (Digicert).

Alternatively. Don't force secure connections, use the "Preferred" setting and leave it as is.

Recent changes

10-01-2022:

  • Move KeepAlive to main VirtualHost configuration
  • Amend note about proxying Plex as TLS.

09-01-2022:

  • Add keepalive to HTTP ProxyPass directive.

07-01-2020:

  • Forcing secure connection info

29-12-2020:

  • Forked original gist
  • Added unsafe-inline to the style-src CSP policy to fix a console error and a mysterious white box!
  • Added Access-Control-Allow-Origin header to prevent CORS errors when the public reverse proxy domain is called by app.plex.tv
  • Wrapped header statements with IfModule

Known issues

  • A 401 Unauthorized request can be seen in developer console for the /media/providers path, it appears to be because the X-Plex-Token query parameter is not present on the request, that it fails.
  • Checking for Updates hangs and takes a very long time to complete through the reverse proxy, but doesn't through app.plex.tv.

Acknowledgements

Based off the original template from hazcod which is based off this post on the Plex Forums.

DEFINE plex_url 127.0.0.1
DEFINE plex_port 32400
DEFINE public_url subdomain.plex.tv
DEFINE email admin@subdomain.plex.tv
# Depending on your Apache configuration/OS you can also set ServerTokens and ServerSignature in another config file so it applies across all VirtualHosts
ServerTokens Prod
SSLStaplingCache "shmcb:${APACHE_LOG_DIR}/stapling-cache(150000)"
SSLSessionCache "shmcb:${APACHE_LOG_DIR}/ssl_scache(512000)"
SSLSessionCacheTimeout 300
<VirtualHost *:80>
ServerName ${public_url}
DocumentRoot /var/www/html
ServerAdmin ${email}
RewriteEngine on
RewriteCond %{SERVER_NAME} =${public_url}
RewriteCond %{HTTPS} off
RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
</VirtualHost>
<VirtualHost *:443>
ServerName ${public_url}
DocumentRoot /var/www/html
ServerAdmin ${email}
ErrorLog ${APACHE_LOG_DIR}/${public_url}.error.log
CustomLog ${APACHE_LOG_DIR}/${public_url}.access.log combined
SSLEngine On
SSLCertificateFile /etc/letsencrypt/live/${public_url}/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/${public_url}/privkey.pem
#Include /etc/letsencrypt/options-ssl-apache.conf
### Forbid the http1.0 protocol ###
Protocols h2 http/1.1
#Options -Includes -ExecCGI
#LimitRequestBody 512000
#FileETag None
#TraceEnable off
Timeout 360
ProxyRequests Off
ProxyPreserveHost On
ProxyTimeout 600
ProxyReceiveBufferSize 4096
SSLProxyEngine On
RequestHeader set Front-End-Https "On"
ServerSignature Off
SSLCompression Off
SSLUseStapling On
SSLStaplingResponderTimeout 5
SSLStaplingReturnResponderErrors Off
# You may want to comment SSLSessionTickets if you get issues with SSL
SSLSessionTickets Off
KeepAlive On
RequestHeader set X-Forwarded-Proto 'https' env=HTTPS
<IfModule mod_headers.c>
Header set Access-Control-Allow-Origin '*'
Header always set Strict-Transport-Security "max-age=15552000; preload"
Header always set X-Content-Type-Options nosniff
Header always set X-Robots-Tag none
Header always set X-XSS-Protection "1; mode=block"
Header always set X-Frame-Options "SAMEORIGIN"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Content-Security-Policy "default-src 'self' https:; font-src 'self' data: ${plex_url} ${public_url}; media-src 'self' blob: data: https: ${plex_url} ${public_url} *.plex.direct *.plex.tv plex.tv; script-src 'self' 'unsafe-inline' 'unsafe-eval' ${plex_url} ${public_url} plex.tv *.plex.tv gstatic.com *.gstatic.com *.plex.direct; style-src 'self' ${plex_url} ${public_url} *.plex.direct 'unsafe-inline'; img-src 'self' data: blob: ${plex_url} ${public_url} plex.tv *.plex.tv *.plex.direct; worker-src *; frame-src 'none'; connect-src 'self' wss: https: ${plex_url} ${public_url} plex.tv *.plex.direct *.plex.tv;"
Header always set Feature-Policy "geolocation 'self'; midi 'self'; sync-xhr 'self'; microphone 'self'; camera 'self'; magnetometer 'self'; gyroscope 'self'; speaker 'self'; fullscreen 'self'; payment 'self'"
</IfModule>
### Use next two for very secure connections ###
SSLHonorCipherOrder On
SSLCipherSuite EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH
SSLProtocol All -SSLv2 -SSLv3 -TLSv1 -TLSv1.1
### Use next two for secure connections and supports more endpoints ###
#SSLCipherSuite EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH:ECDHE-RSA-AES128-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA128:DHE-RSA-AES128-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES128-GCM-SHA128:ECDHE-RSA-AES128-SHA384:ECDHE-RSA-AES128-SHA128:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES128-SHA128:DHE-RSA-AES128-SHA128:DHE-RSA-AES128-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA384:AES128-GCM-SHA128:AES128-SHA128:AES128-SHA128:AES128-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4
#SSLProtocol All -SSLv2 -SSLv3 -TLSv1 -TLSv1.1
### Actually proxy the traffic and really the only important part ###
ProxyPassMatch ^/.well-known !
ProxyPass / http://${plex_url}:${plex_port}/
ProxyPassReverse / http://${plex_url}:${plex_port}/
ProxyPass /:/ ws://${plex_url}:${plex_port}/:/
ProxyPassReverse /:/ ws://${plex_url}:${plex_port}/:/
ProxyPass /:/ wss://${plex_url}:${plex_port}/:/
ProxyPassReverse /:/ wss://${plex_url}:${plex_port}/:/
LimitRequestBody 512000
FileETag None
TraceEnable off
#Header edit Set-Cookie ^(.*)$ ;HttpOnly;Secure
Timeout 60
<Location /:/websockets/notifications>
ProxyPass wss://${plex_url}:${plex_port}/:/websockets/notifications
ProxyPassReverse wss://${plex_url}:${plex_port}/:/websockets/notifications
</Location>
<Proxy *>
Require all granted
</Proxy>
RewriteEngine on
RewriteCond %{REQUEST_URI} !^/web
RewriteCond %{HTTP:X-Plex-Device} ^$
RewriteCond %{REQUEST_METHOD} !^(OPTIONS)$
RewriteCond %{QUERY_STRING} (^|&)X-Plex-Device=(&|$) [OR]
RewriteCond %{QUERY_STRING} !(^|&)X-Plex-Device=
RewriteRule ^/$ /web/$1 [R,L]
</VirtualHost>
@dieechtenilente
Copy link

Pretty neat! Gets rid of the strange box in the top left corner :)

@jamesmacwhite
Copy link
Author

@dieechtenilente Hi. Yes, this is due to a CSP related issue, which has occurred more recently. It requires a change to style-src,

It is still heavily based on the original gist, but just provides a bit more context to the overall VirtualHost. This also enabled KeepAlive, which may help with streaming.

@Nick0703
Copy link

Is the remote bandwidth working for you on the dashboard? It's not showing up for me.
image

@jamesmacwhite
Copy link
Author

@Nick0703 Yes. I'd check if there's any console errors on the bandwidth page that could explain why it's not logging remote WAN activity. One difference might be that I now force secure connections for clients, it appears those clients are not using encryption.

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment