Working apache reverse proxy configuration for Plex!

I tried to setup plex (with docker) behind a reverse proxy, so Plex is only accessible from https://plex.website.com/ and with my custom certificate. I had some troubles along the way, but in the end, I made it work!

Just wanted to share for you all. :slight_smile:
A big shout-out to rcombs for letting me know Plex.tv does an OPTIONS on your web server.

Link: Apache2 reverse proxy vhost configuration for Plex. Rerquires modules ssl, proxy, wstunnel · GitHub
(The link will typically have the latest version.)

`
ServerSignature Off
ServerTokens Prod

<VirtualHost *:80>
ServerName plex.website.com

This VirtualHost redirects everything to HTTPS on port 443.

RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI}

<VirtualHost *:443>
ServerName plex.website.com
ServerAlias “”
Options -Includes -ExecCGI

RewriteEngine On
RewriteCond %{THE_REQUEST} !HTTP/1.1$
RewriteRule .* - [F]

LimitRequestBody 512000
SSLEngine On
SSLCertificateFile /etc/apache2/ssl/cert.pem
SSLCertificateKeyFile /etc/apache2/ssl/privkey.pem
SSLProtocol +TLSv1.2

Header always set Strict-Transport-Security “max-age=63072000; includeSubdomains; preload”
Header always set X-Frame-Options DENY
FileETag None
TraceEnable off
Header edit Set-Cookie ^(.*)$ ;HttpOnly;Secure
Header set X-XSS-Protection “1; mode=block”
Timeout 60

<Location /:/websockets/notifications>
    ProxyPass wss://plex:32400/:/websockets/notifications #plex here is resolved to my plex container
    ProxyPassReverse wss://plex:32400/:/websockets/notifications #plex here is resolved to my plex container
</Location>

<Proxy *>
Order deny,allow
Allow from all

ProxyRequests Off
ProxyPreserveHost On
ProxyPass / http://plex:32400/ #plex here is resolved to my plex container
ProxyPassReverse / http://plex:32400/ #plex here is resolved to my plex container

RewriteEngine on
RewriteCond %{REQUEST_URI} !^/web
RewriteCond %{HTTP:X-Plex-Device} ^$
RewriteCond %{REQUEST_METHOD} !^(OPTIONS)$
RewriteRule ^/$ /web/$1 [R,L]
`

Very cool. I have a few questions though. Doesn’t this config open up your server to the entire world? (How) Are you doing access control?

Finally, I assume that this setup only works with a browser and not any plex apps, correct?

could you explain the procedure to implement this in apache?

matteos, it depends on your operating system but most apache setups have something along the lines of Include *.conf in some directory. In debian like systems (ubuntu, mint) you can add a new file called maybe plex.conf to /etc/apache2/sites-available/ and then run a2ensite plex. On redhat like systems (fedora, centos) just add that file to /etc/httpd/conf.d/. On windows I’m not sure, it would depend where you got it from, but likely there is a folder like c:\apache\conf.d or something similar.

@hazcod I tried your config, but it doesn’t seem to work. When going to plex.mysite.com, I can login, but afterwards my server never shows up. Only works on the same network. Any ideas?

Seems way too complicated when you can just

<VirtualHost *:80>
ServerName your servername
Redirect / https://your servername/
RewriteEngine on
RewriteCond %{SERVER_NAME} =your servername
RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
</VirtualHost>

<IfModule mod_ssl.c>
<VirtualHost *:443>
ProxyPass /plex http://localhost:32400
ProxyPassReverse /plex http://localhost:32400
ProxyPass /web http://localhost:32400/web
ProxyPassReverse /web http://localhost:32400/web
</VirtualHost>
</IfModule>

1 Like

First off, the apache2 conf file is to have a web interface that is exactly like being on localhost:32400. You still must have port forwarding set up and wherever you are trying to access plex from must have that port open in order for everything to work properly. If you are slightly paranoid about security, you can use this (my) apache2 conf file:
EDIT 5/9/18: Added blob: to media-src in Content-Security-Policy to allow trailers to play.

EDIT: Update as of 4/17/2019.
You will need to be using Apache2 >= 2.4.17 to use this and several mods (proxy, ssl, proxy_wstunnel, http, dir, env, headers, proxy_balancer, proxy_http, rewrite I think is all of them).

EDIT: Update as of 2/18/2021.

  • Updated Content Security Policy
  • Added Permissions Policy
  • Removed Rewrite
  • Added <Location> sections
  • gzip defalte for common files (javascript, xml, etc.)
  • Updated Ciphers

New config (February 2021)

    DEFINE plex_url 192.168.1.22
    DEFINE plex_port 32400
    DEFINE serv_name plex.domain.com
    ServerTokens Prod
    SSLStaplingCache "shmcb:${APACHE_LOG_DIR}/stapling-cache(150000)"
    SSLSessionCache "shmcb:${APACHE_LOG_DIR}/ssl_scache(512000)"
    SSLSessionCacheTimeout 300
### If you have Google's Mod PageSpeed, disable it ###
    <IfModule mod_pagespeed_ap24.c>
        ModPagespeed Off
    </IfModule>
<VirtualHost *:80>
    ServerName ${serv_name}
    Redirect / https://plex.domain.com
    ErrorLog ${APACHE_LOG_DIR}/plex.error.log
    CustomLog ${APACHE_LOG_DIR}/plex.access.log combined
</VirtualHost>
<VirtualHost *:443>
    ServerName ${serv_name}
    DocumentRoot /var/www/html
    ErrorLog ${APACHE_LOG_DIR}/plex.error.log
    CustomLog ${APACHE_LOG_DIR}/plex.access.log combined
### Let's Encrypt Section ###
    SSLCertificateFile /etc/letsencrypt/live/domain.com/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/domain.com/privkey.pem
### Deny http1.0 requests ###
    Protocols h2 http/1.1
### Harden Security ###
    ProxyRequests Off
    ProxyPreserveHost On
    ProxyTimeout 600
    SSLProxyEngine On
    RequestHeader set Front-End-Https "On"
    ServerSignature Off
    SSLCompression Off
    SSLUseStapling On
    SSLStaplingResponderTimeout 20
    SSLStaplingReturnResponderErrors Off
    SSLSessionTickets Off
### Add headers ###
    RequestHeader set X-Forwarded-Proto 'https' env=HTTPS
    Header always set Strict-Transport-Security "max-age=15552000;"
    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 "same-origin"
    Header always set Permissions-Policy "geolocation=(self), midi=(self), sync-xhr=(self), microphone=(self), camera=(self), magnetometer=(self), gyroscope=(self), fullscreen=(self), payment=(self)"
### Content Security Policy for the overly paranoid - may break with Plex server updates (works with server version 1.21.3.4046 and 1.21.3.4021) ###
    Header always set Content-Security-Policy "default-src 'none'; base-uri 'self' ${serv_name}; font-src 'self' data: ${serv_name}; media-src 'self' data: blob: ${serv_name} https://*.plex.direct:32400 https://video.internetvideoarchive.net https://*.cloudfront.net; script-src 'self' 'unsafe-inline' 'unsafe-eval' domain.com ${serv_name}; style-src 'self' 'unsafe-inline' ${serv_name}; img-src 'self' data: blob: https: ${serv_name}; worker-src * blob:; frame-src 'self'; connect-src 'self' https: domain.com ${serv_name} wss://*.plex.direct:32400 wss://pubsub.plex.tv; object-src 'self' ${serv_name}; frame-ancestors 'self' domain.com ${serv_name}; form-action 'self' ${serv_name}; manifest-src 'self' ${serv_name}; script-src-elem 'self' 'unsafe-inline' domain.com ${serv_name} www.gstatic.com"
### Add secure ciphers ###
    SSLCipherSuite TLS-CHACHA20-POLY1305-SHA256:TLS-AES-256-GCM-SHA384:TLS-AES-128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256
### Only enable TLSv1.2 and TLSv1.3 ###
    SSLProtocol All -SSLv2 -SSLv3 -TLSv1 -TLSv1.1
### Tell clients to use server defined ciphers and orders ###
    SSLHonorCipherOrder On
### If path is for Let's Encrypt, don't proxy ###
    ProxyPassMatch ^/.well-known !
### Plex Specific Section ###
 ## Plex has A LOT of javascript, xml and html. This helps a lot, but if it causes playback issues with devices, disable this section
    <IfModule mod_deflate.c>
        AddOutputFilterByType DEFLATE text/html
        AddOutputFilterByType DEFLATE text/plain
        AddOutputFilterByType DEFLATE text/css
        AddOutputFilterByType DEFLATE application/javascript
        AddOutputFilterByType DEFLATE text/javascript
        AddOutputFilterByType DEFLATE application/x-javascript
        AddOutputFilterByType DEFLATE image/svg+xml
        AddOutputFilterByType DEFLATE image/x-icon
        AddOutputFilterByType DEFLATE application/vnd.ms-fontobject
        AddOutputFilterByType DEFLATE application/x-font
        AddOutputFilterByType DEFLATE application/x-font-opentype
        AddOutputFilterByType DEFLATE application/x-font-otf
        AddOutputFilterByType DEFLATE application/x-font-truetype
        AddOutputFilterByType DEFLATE application/x-font-ttf
        AddOutputFilterByType DEFLATE font/opentype
        AddOutputFilterByType DEFLATE font/otf
        AddOutputFilterByType DEFLATE font/ttf
        AddOutputFilterByType DEFLATE application/rss+xml
        AddOutputFilterByType DEFLATE application/xhtml+xml
        AddOutputFilterByType DEFLATE application/xml
        AddOutputFilterByType DEFLATE text/xml
        BrowserMatch ^Mozilla/4 gzip-only-text/html
        BrowserMatch ^Mozilla/4\.0[678] no-gzip
        BrowserMatch \bMSIE !no-gzip !gzip-only-text/html
        Header append Vary User-Agent
    </IfModule>
 ## Proxy all web traffic here ## 
    <Location />
        ProxyPass http://${plex_url}:${plex_port}/
        ProxyPassReverse http://${plex_url}:${plex_port}/
    </Location>
 ## Proxy all websocket requests here ## 
    <Location /:/>
        ProxyPass wss://${plex_url}:${plex_port}/:/
        ProxyPassReverse wss://${plex_url}:${plex_port}/:/
    </Location>
### Don't know if we still need this ###
    <Proxy *>
        Order deny,allow
        Allow from all
    </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>

Need URL subpath?

Change to this:

    <Location /plex/>
        ProxyPass http://${plex_url}:${plex_port}/
        ProxyPassReverse http://${plex_url}:${plex_port}/
    </Location>
 ## Proxy all websocket requests here ## 
    <Location /plex/:/>
        ProxyPass wss://${plex_url}:${plex_port}/:/
        ProxyPassReverse wss://${plex_url}:${plex_port}/:/
    </Location>

Old config (April 2019)

<IfModule mod_ssl.c>
	DEFINE plex_url 192.168.1.22
	DEFINE plex_port 32400
	DEFINE serv_name plex.domain.com
	ServerTokens Prod
	SSLStaplingCache "shmcb:${APACHE_LOG_DIR}/stapling-cache(150000)"
	SSLSessionCache "shmcb:${APACHE_LOG_DIR}/ssl_scache(512000)"
	SSLSessionCacheTimeout 300
	ModPagespeed Off
<VirtualHost *:80>
	ServerName ${serv_name}
	DocumentRoot /var/www/html
	ServerAdmin aw@hell.no
	RewriteEngine On
	RewriteCond %{SERVER_NAME} =${serv_name}
	RewriteCond %{HTTPS} Off
	RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
	ErrorLog ${APACHE_LOG_DIR}/${serv_name}.error.log
	CustomLog ${APACHE_LOG_DIR}/${serv_name}.access.log combined
</VirtualHost>
<VirtualHost *:443>
	ServerName ${serv_name}
	DocumentRoot /var/www/html
	ServerAdmin aw@hell.no
	ErrorLog ${APACHE_LOG_DIR}/${serv_name}.error.log
	CustomLog ${APACHE_LOG_DIR}/${serv_name}.access.log combined
### Let's Encrypt Section ###
	SSLCertificateFile /etc/letsencrypt/live/${serv_name}/fullchain.pem
	SSLCertificateKeyFile /etc/letsencrypt/live/${serv_name}/privkey.pem
	#Include /etc/letsencrypt/options-ssl-apache.conf
	Options -Includes -ExecCGI
### Deny http1.0 requests ###
	RewriteEngine On
	RewriteCond %{SERVER_PROTOCOL} ^HTTP/1\.0$
	#RewriteCond %{REQUEST_URI} !^/404/$
	RewriteRule ^ - [F]
### Harden Security ###
	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
	SSLSessionTickets Off
	RequestHeader set X-Forwarded-Proto 'https' env=HTTPS
	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 "same-origin"
	Header always set Feature-Policy "accelerometer 'none'; camera 'none'; geolocation 'none'; gyroscope 'none'; magnetometer 'none'; microphone 'none'; payment 'none'; usb 'none';"
	Header always set Content-Security-Policy "default-src 'self' https:; font-src 'self' data: ${plex_url} ${serv_name}; media-src 'self' blob: ${plex_url} ${serv_name}; script-src 'self' 'unsafe-inline' ${plex_url} ${serv_name} plex.tv www.gstatic.com; style-src 'self' ${plex_url} ${serv_name}; img-src 'self' data: blob: ${plex_url} ${serv_name} plex.tv *.plex.tv; worker-src *; frame-src 'none'; connect-src 'self' wss: https: ${plex_url} ${serv_name} plex.tv *.plex.direct *.plex.tv;"
	SSLCipherSuite ECDHE+RSA+AES256+GCM+SHA512:DHE+RSA+AES256+GCM+SHA512:ECDHE+RSA+AES256+GCM+SHA384:DHE+RSA+AES256+GCM+SHA384:ECDHE+RSA+AES256+SHA384:EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4
	SSLProtocol All -SSLv2 -SSLv3 -TLSv1 -TLSv1.1
	SSLHonorCipherOrder On
### Plex Specific Section ###
	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 *>
		Order deny,allow
		Allow from all
	</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>
</IfModule>
4 Likes