You were very close. I am posting my answer for those who are searching and stumble upon this post. You will need to be using Apache2 >= 2.4.17 to use this and several enabled mods (proxy, ssl, proxy_wstunnel, http, dir, defalte, env, headers, proxy_balancer, proxy_http, rewrite I think are all of them):
Edit August 2019
Added some new information and now the below configuration enables you to watch trailers and hear TV Show theme music.
Edit February 2021
- Updated Content Security Policy
- Added Permissions Policy
- Removed Rewrite
- Added
<Location>
sections
- gzip defalte for common files (javascript, xml, etc.)
- Updated Ciphers
With SSL (new February 2021)
DEFINE plex_url 192.168.1.9
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 ###
SSLEngine On
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>
With SSL (old August 2019)
DEFINE plex_url 127.0.0.1
DEFINE plex_port 32400
DEFINE public_url subdomain.plex.tv
DEFINE email admin@subdomain.plex.tv
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
#ModPagespeed Off
<VirtualHost *:80>
ServerName ${public_url}
DocumentRoot /var/www/offline
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/offline
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
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 "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; 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;"
## If you want to be safer, remove the 'unsafe-inline' 'unsafe-eval' from above and use Chrome to get the sha-256 sums and input below (below was for Server version: 1.16.5.1488; Web version: 3.108.2)
#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' 'sha256-8yKKbip2qr14RHV8H1qDEbRAm9Mmf5ePeQh+wB5pMCw=' 'sha256-pKO/nNgeauDINvYfxdygP3mGssdVQRpRNxaF7uPRoGM=' 'sha256-mrLkgfrqAhdxc2TvIODT0I7QtvuQLMS9AgtfLL9eMXo=' ${plex_url} ${public_url} plex.tv *.plex.tv gstatic.com *.gstatic.com *.plex.direct; style-src 'self' ${plex_url} ${public_url} *.plex.direct; 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'"
### 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 ###
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>
Without SSL
DEFINE plex_url 192.168.1.22
DEFINE plex_port 32400
DEFINE serv_name plex.domain.com
ServerTokens Prod
<VirtualHost *:80>
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
Options -Includes -ExecCGI
### Deny http1.0 requests ###
RewriteEngine On
RewriteCond %{SERVER_PROTOCOL} ^HTTP/1\.0$
#RewriteCond %{REQUEST_URI} !^/404/$
RewriteRule ^ - [F]
### 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 ws://${plex_url}:${plex_port}/:/websockets/notifications
ProxyPassReverse ws://${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>
Need this setup but for nginx?