2

I have a Plex server running at website.com:32400/web. I would like to be able to access this at plex.website.com using Apache's reverse proxy capabilities. The following is (the relevant parts of) my /etc/apache2/sites-available/plex.website.com.conf file:

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

        ProxyRequests off
        ProxyPass / http://website.com:32400/web/
        ProxyPassReverse / http://website.com:32400/web/
        ProxyHTMLURLMap http://website.com:32400/web http://plex.website.com
        <Proxy *>
                Order deny,allow
                Allow from all
        </Proxy>
</VirtualHost>

This setup works partially; that is, if I go to plex.website.com/index.html then I get served the exact same HTML as I do when I go to website.com:32400/web/index.html. However, beyond that, everything else seems to 404 (i.e. CSS and JavaScript). For example, http://website.com:32400/web/img/desktop/ios/icon-iphone.png loads fine, but http://plex.website.com/web/img/desktop/ios/icon-iphone.png produces a 404 error.

It seems I can access anything directly in the web directory though, such as plex.website.com/favicon.ico which loads fine. So clearly it's a problem specifically with mapping subdomains.

All necessary Apache modules are enabled.

I'm not really sure how I'd begin trying to fix this.

Edit: I've just realised the reason it's 404'ing is that it's still including the web part of the URL when it shouldn't be. In the example I gave, http://plex.website.com/web/img/desktop/ios/icon-iphone.png is the location it's trying to access - but this should be http://plex.website.com/img/desktop/ios/icon-iphone.png instead. So I suspect it's a problem with the ProxyHTMLURLMap.

clb
  • 715
  • 9
  • 23

1 Answers1

6

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?

Community
  • 1
  • 1
iamdoubz
  • 121
  • 1
  • 10
  • Is above configuration enough for "Remote Access" feature to start working, or do I still need to forward port 32400 on the router as to [this article](https://support.plex.tv/articles/200931138-troubleshooting-remote-access/)? – dma_k Sep 09 '22 at 16:44
  • 1
    Short answer no: you would still need to forward port 32400 to your router. Long answer, you could technically set up your plex server to allow passwordless logins from your reverse proxy. But, I have not tested this and it is simply put easier to forward a port. Good luck! – iamdoubz Sep 14 '22 at 20:34
  • Many thanks for that hint – setting up a reverse proxy does not bring many advantages. When you say "With SSL" – does it mean that *Settings → Network → Secure connections* option in Plex should be set to *Required*? I am asking because it is not clear how Apache proxy should communicate with local Plex server as you suggest to forward only secured traffic (`wss://`)? I would think that HTTPs should be unwrapped by Apache and behind it the traffic is unsecured. – dma_k Sep 15 '22 at 12:38
  • The only difference between with and without SSL is the SSL one forwards/redirects traffic from port 80 to 443. The reason for a reverse proxy is to leave Plex server "untouched" and have Apache handle the certificates instead of setting all that up in Settings, Network (I think). In theory, you can proxy all your traffic over 443 (including :32400) to get by restrictive firewalls, but for most people that is overkill. – iamdoubz Sep 16 '22 at 18:48