Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Apache2 reverse proxy vhost configuration for Plex. Rerquires modules ssl, proxy, wstunnel
This current configuration is based of at least Server Version 1.16.5.1488 and Web Version: 3.108.2.
This updated config file allows the playing of trailers and TV Show theme music where as the previous one did not.
## Requirements
1. Apache version > 2.4
2. A bunch of mod's enabled (proxy, ssl, proxy_wstunnel, http, dir, env, headers, proxy_balancer, proxy_http, rewrite)
3. Protocols h2 http/1.1 needs apachectl -V 2.4.17 and higher...
## Apache .conf file
```
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 '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'"
### 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>
```
@oucil
Copy link

oucil commented Mar 19, 2020

@hazcod my pleasure. As a related aside, has anyone ever had any luck with a reverse proxy setup that can fool Plex entirely into thinking the requests are all from localhost, so that the "Claim Server" process can be run from a remote address? I've been trying all sorts of reverse proxy vhost setups to mimic the SSH Tunnel approach to claiming a remote server, but nothing works. Somehow it can always figure out that the request via the proxy isn't local. Any ideas?

@iamdoubz
Copy link

iamdoubz commented Mar 24, 2020

@iamdoubz @hazcod Appreciate the work you guys have done keeping this up to date! Noticed something I thought I'd point out. I'm not sure how this is working for you in 2.4 unless you're using mod_access_compat (which isn't one of your listed requirements)...

<Proxy *>
	Order deny,allow
	Allow from all
</Proxy>

The new directive should be...

<Proxy *>
  Require all granted
</Proxy>

... should it not?

Good catch.

@Steve8291
Copy link

Steve8291 commented Dec 24, 2020

Thank you for the great work on this.
I'm trying to do something a bit different and was wondering if anyone had any thoughts on it. I wanted to run incoming traffic to plex through an apache proxy while allowing outgoing traffic to go out as usual. The reasoning was to be able to monitor ip addresses with the modsecurity application firewall in apache. So I don't actually have a different url that I'm proxying through apache. All incoming traffic to port 32400 hits my iptables rules and redirects. I think what is really messing things up is that I'm still trying to use SSL certs generated by the plex external servers (the way plex intends you to do it)
I've set up a nat PREROUTING firewall rule to redirect incoming traffic on the plex port 32400 to a different port 60000 that apache will listen on.

In Apache I've got a VirtualHost:

<VirtualHost _default_:60000>
	ProxyRequests Off
	ProxyPreserveHost On

	ProxyPass "/" "http://localhost:32400" connectiontimeout=5 timeout=30 keepalive=on
	ProxyPass "/:/" "ws://http://localhost:32400/" connectiontimeout=5 timeout=30 keepalive=on
	ProxyPass "/:/" "wss://http://localhost:32400/" connectiontimeout=5 timeout=30 keepalive=on
</VirtualHost>

Requests seem to be hitting the apache server but are met with a 400 status code and go no further. I'm getting requests that look like this in my apache access.log:
[Thu Dec 24 11:44:13 2020] 34.248.59.52 - "\x16\x03\x01\x02" 400 "-" "-" [-]
Which is plex trying to authenticate with the start of a TLS Handshake.
Anyone know what I need to do to allow those to pass through to plex?

@jamesmacwhite
Copy link

jamesmacwhite commented Dec 28, 2020

A few observations with the reverse proxy setup. Streaming works fine with latest stable Plex release, when browsing the reverse proxy URL the following errors are observed in console, they don't occur on the app.plex.tv URL, so assuming specific to reverse proxy.

  1. The CSP policy might need unsafe-inline adding to style-src
  2. wss://plex.example.com/:/websockets/notifications?X-Plex-Token=xxxxxxxxxxxxxxxxxxx' failed: Error during WebSocket handshake: Unexpected response code: 500 is thrown
  3. 401 Unauthorized error for this request: https://plex.example.com/media/providers?X-Plex-Product=Plex%20Web&X-Plex-Version=4.47.3&X-Plex-Client-Identifier=xxxxxxxxxxxxxxxxxxxxxxxxx&X-Plex-Platform=Chrome&X-Plex-Platform-Version=87.0&X-Plex-Sync-Version=2&X-Plex-Features=external-media%2Cindirect-media&X-Plex-Model=bundled&X-Plex-Device=Windows&X-Plex-Device-Name=Chrome&X-Plex-Device-Screen-Resolution=926x969%2C1920x1080&X-Plex-Language=en-GB
  4. 401 Unauthorized error for this request: https://plex.example.com/?X-Plex-Product=Plex%20Web&X-Plex-Version=4.47.3&X-Plex-Client-Identifier=xxxxxxxxxxxxxxxxxxxx&X-Plex-Platform=Chrome&X-Plex-Platform-Version=87.0&X-Plex-Sync-Version=2&X-Plex-Features=external-media%2Cindirect-media&X-Plex-Model=bundled&X-Plex-Device=Windows&X-Plex-Device-Name=Chrome&X-Plex-Device-Screen-Resolution=926x969%2C1920x1080&X-Plex-Language=en-GB
  5. You might want to set the Access-Control-Allow-Origin header so you don't get CORS errors, if you access via app.plex.tv and it calls your reverse proxy domain with Header set Access-Control-Allow-Origin '*'. You can't use "always set" as it will double up on the plex.direct URL, which app.plex.tv will complain about, alternatively for Apache 2.4 Header setifempty Access-Control-Allow-Origin "*" should work.

For the two 401 unauthorised requests, it looks like these are missing the X-Plex-Token parameter.

@felmey
Copy link

felmey commented Jan 11, 2021

Thanks for updating this. My reverse proxy was freezing at the plex spalsh screen until I imported this update.

Now, after the splash screen, I am getting a weird box outline in the upper left corner of the plex player when using this setup. It is mostly covered up eventually when the player top and sidebar appear. However, there is still a portion visible.

Based on jamesmacwhite's post above I accessed the plex server through app.plex.tv and there was no weird box. Nor is it present when I bypass the reverse proxy and just use the ip address http://192.168.x.x:32400/web ... It only displays when using my reverse proxy.

Any ideas on how I might get rid of it?
Screenshot from 2021-01-11 10-47-40

@jamesmacwhite
Copy link

jamesmacwhite commented Jan 11, 2021

@felmey I've seen the white box as well, but it's since gone away. I hadn't realised I've accidentally fixed this until your post. It is due to the CSP policy. You need add unsafe-inline to the style-src of the current CSP policy provided by this config. In my previous post, I was analysing some of the errors in console under the reverse proxy compared to app.plex.tv and I hadn't realised the white box and the CSP policy were connected until you pointed it out.

@felmey
Copy link

felmey commented Jan 12, 2021

You need add unsafe-inline to the style-src of the current CSP policy provided by this config.

@jamesmacwhite Thanks! That worked 100%. The box is gone after making that addition.

@hazcod
Copy link
Author

hazcod commented Jan 12, 2021

I've adapted the gist to include style-src: unsafe-inline.

@W3AXL
Copy link

W3AXL commented Feb 6, 2021

Is there a current guide for the proper way to generate Letsencrypt certs for the server? This and most other guides assume you already have your certificates generated and ready to use, and I haven't been able to find anything that mentions the process for generating these certs in the first place.

Obviously the usual certbot approach doesn't work for a custom setup like this, which is why I'm confused.

Update: Success! To use certbot, I set up my virtualhost to simply point to a directory where certbot could write its verification files. Once I had the certificates generated, I used the above virtualhost config and everything seems to be working properly.

One final question - how will renewal work with certbot? Doesn't it need to perform another http verification when renewing the certificates?

@jamesmacwhite
Copy link

jamesmacwhite commented Feb 6, 2021

@W3AXL

Certbot should automatically have installed cronjob to regularly check for when the certificate needs to be renewed. This VirtualHost template actually handles being able to perform HTTP verification by bypassing the typical .well-known pathl, to avoid the request being sent to your Plex server, instead it should be processed by your web server and allow certbot to verify.

To be honest, I'm not sure why the template uses DocumentRoot /var/www/offline, it would be better to use the typical default of /var/www/html

@iamdoubz
Copy link

iamdoubz commented Feb 18, 2021

Another update. I was working on a nginx conf for Plex.

UPDATE: This code that was here.... wasn't very good.

@jamesmacwhite
Copy link

jamesmacwhite commented Apr 14, 2021

@iamdoubz You might want to review this configuration. It caused a few regressions with my reverse proxy setup. Remote streams worked but they were not picked up in the Now playing status even though I could see remote stream traffic and watch status i.e. watched and unwatched was broken amongst other things like resuming was not working. Reverting the Location and ProxyPass/ProxyPassReverse parts allows things to work again fine. Local clients were not affected.

I'd advise others to use the previous configuration due to the above. I'm thinking in particularly it's the proxying parts.

@tyjtyj
Copy link

tyjtyj commented Sep 14, 2021

Anyone still using this? It seems stopped working(old and new version) last few days without any changes on the my setup.
There are lots of /:/websockets/notifications error in the logs too.

Client player cant fastforward / timeout/ unepected error on remote stream.

@iamdoubz
Copy link

iamdoubz commented Sep 15, 2021

Don't use the Apache "Location" version. It was bad and I removed it from my previous post. My plex config still seems to be working with web version 4.62.1 and server version 1.24.2.4973.

@Tipz
Copy link

Tipz commented Sep 16, 2021

Anyone still using this? It seems stopped working(old and new version) last few days without any changes on the my setup.
There are lots of /:/websockets/notifications error in the logs too.

Client player cant fastforward / timeout/ unepected error on remote stream.

What version of Plex do you have?
For Plex version 4.62.1 (version server 1.24.2.4973) - works, but I had to edit "Header always set Content-Security-Policy"

@iamdoubz
Copy link

iamdoubz commented Sep 16, 2021

If you connect directly to Plex: http://ip.ad.re.ss:32400/web, do you experience these weird timeouts and/or transcoding problems?

@tyjtyj
Copy link

tyjtyj commented Sep 16, 2021

If you connect directly to Plex: http://ip.ad.re.ss:32400/web, do you experience these weird timeouts and/or transcoding problems?

no issue when open port directly to plex(even via cloudflare. Issue only when i go thru apache proxy.

UPDATE: Found the root cause to be traffic proxy to plex docker seems to causing the timeout.

@jamesmacwhite
Copy link

jamesmacwhite commented Sep 18, 2021

Reviewing my reverse proxy the CSP change needed is to do with img-src now, it seems images are now being served from a path of:

https://ip.hash.plex.direct, the rule of *.plex.direct no longer matches, because of the hash, subdomain of subdomain, you can probably get away with just allowing https: for img-src to avoid having to add loads of specific rules.

Otherwise, it works fine and can play content through the web and mobile app, no changes other than CSP.

@Tipz
Copy link

Tipz commented Sep 18, 2021

For my configuration, a proxy server - it was enough to add the *.plex.direct:32400 for img-src

@jamesmacwhite
Copy link

jamesmacwhite commented Sep 18, 2021

@Tipz Ah thanks, yes that's a better rule if you want to be selective. You could still use https: generally to avoid having all of the conditions, some of them might not be valid anymore.

@jamesmacwhite
Copy link

jamesmacwhite commented Oct 8, 2021

@hazcod The header 'Feature-Policy' can be renamed to 'Permissions-Policy' due to the specification change, it may need reformatting as the specification changed.

@simao-silva
Copy link

simao-silva commented Jan 9, 2022

Does anyone know how to fix the errors regarding /:/websockets/notifications? I am constantly getting the videos freezing and the logs showing Error during SSL Handshake with remote server returned by /:/websockets/notifications.

@jamesmacwhite
Copy link

jamesmacwhite commented Jan 9, 2022

@simao-silva That would suggest the WebSocket requests are not being properly proxied. Do any console errors show up related to WebSocket issues when browsing your reverse proxy domain?

As a debugging step you could try applying the following:

SSLProxyEngine on
SSLProxyVerify none 
SSLProxyCheckPeerCN off
SSLProxyCheckPeerName off
SSLProxyCheckPeerExpire off

However, be mindful this basically disables any SSL/TLS validation, so don't leave enabled but for testing purposes.

Does your reverse proxy have a valid SSL certificate?

@tyjtyj
Copy link

tyjtyj commented Jan 9, 2022

After long battle with freezing video. I fixed it with few changes. I am not sure which one really fixed it.

On plex ensure the certificate.pfx is set to my to domain generated. U need to put full path from root
Custom certificate location: /var/lib/plexmediaserver/certificate.pfx
Debug and restart plex make sure the cert is used. clear the cert cache if needed(i forgot how)

https instead of http.

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

Keep alive on. I got random proxy timeout without this. Seems there is too much thread opened. Thus keepalive on to prevent that.

	KeepAlive on

I dont have error after make these setting.
Server version: Apache/2.4.51 (Debian)

@jamesmacwhite
Copy link

jamesmacwhite commented Jan 9, 2022

@tyjtyj Interesting, thanks for sharing. Adding keepalive to the ProxyPass might be something to add to the main config.

It is worth pointing out if you change the ProxyPass/ProxyPassReverse to https, you must configure a valid SSL certificate. I believe this means configuring a custom one your Plex instance, otherwise you will get "Error during SSL Handshake" errors. This will be because of a common name mismatch. The configuration detailed here, typically focuses on providing a valid SSL certificate for the proxy, but not for the traffic between the proxy, which is why it's using plain http.

You can technically change the proxy to https:// without doing this but you'll have to disable one or more SSL checks, which is not recommended due to higher risk of MITM, however if the traffic is being proxied locally, then it's not as bad.

@iamdoubz
Copy link

iamdoubz commented Feb 1, 2022

Below is a "test" config for using Plex as a subdirectory. It needs an additional apache2 mod called proxy_html and assumes a subdirectory of "plex":

### Plex subdirectory test
    RewriteEngine on
    RewriteCond %{REQUEST_URI} !^/plex/web
    RewriteCond %{HTTP:X-Plex-Device} ^$
    RewriteCond %{REQUEST_METHOD} !^(OPTIONS)$
    RewriteCond %{QUERY_STRING} (^|&)X-Plex-Device=(&|$) [OR]
    RewriteCond %{QUERY_STRING} !(^|&)X-Plex-Device=
    RewriteRule ^/$ /plex/web/$1 [R,L]
<Location /web>
    ProxyPass http://192.168.1.109:32400/web retry=0
    ProxyPassReverse http://192.168.1.109:32400/web
    SetOutputFilter proxy-html
    ProxyHTMLURLMap http://192.168.1.109:32400/web
</Location>
RewriteRule ^/web$ /plex/web/ [R]
<Location /media>
    ProxyPass http://192.168.1.109:32400/media retry=0
    ProxyPassReverse http://192.168.1.109:32400/media
    SetOutputFilter proxy-html
    ProxyHTMLURLMap http://192.168.1.109:32400/media
</Location>
RewriteRule ^/media$ /plex/media/ [R]
<Location /plex/:>
    ProxyPass wss://192.168.1.109:32400/:
    ProxyPassReverse wss://192.168.1.109:32400/:
</Location>
RewriteRule ^/plex/:$ /plex/:/ [R]
<Location /plex>
    ProxyPass http://192.168.1.109:32400 retry=0
    ProxyPassReverse http://192.168.1.109:32400
    SetOutputFilter proxy-html
    ProxyHTMLURLMap http://192.168.1.109:32400
</Location>
RewriteRule ^/plex$ /plex/ [R]

In my testing, it appears to be working. Tested playing some music and hearing the TV Show theme music play. Make sure you change the 192.168.1.109:32400 to the IP and port of your Plex server.

@chrihuc
Copy link

chrihuc commented Mar 4, 2022

Very cool of you to share the config.
I tried it but I cannot get it to work.

If I set it up with the modifications needed for my system I only get a 404 page not found.
When I modify the http to https here:
ProxyPass / https://${plex_url}:${plex_port}/
ProxyPassReverse / https://${plex_url}:${plex_port}/
I get Error during SSL Handshake with remote server

If I then add these lines:
SSLProxyEngine on
SSLProxyVerify none
SSLProxyCheckPeerCN off
SSLProxyCheckPeerName off
SSLProxyCheckPeerExpire off

I get again the 404.

Any Idea where I'm doing something wrong?

Thanks Chris

@jamesmacwhite
Copy link

jamesmacwhite commented Mar 5, 2022

@chrihuc Is this proxying a subdomain or sub directory? Check that you can access the path being proxied outside of Apache first. 404 suggests it's hitting something, but maybe not what you think.

The SSL handshake error is expected without additional configuration not covered here, as the SSL certificate you provide for a custom domain is not going to match the Plex domain that is proxied. For simplicity, just proxy HTTP, but you'll have to keep Plex secure connections as "preferred" and not set it to "forced". Also be aware disabling all those SSL directives is a security risk. You actually only need SSLProxyCheckPeerName off to avoid the 502 error.

@chrihuc
Copy link

chrihuc commented Mar 7, 2022

I'm proxying a subdomain, doing this already for Grafana.
Anyway I was about to look into it again right now, and guess what, it works... I have not changed anything. Not sure if I had still a bad route in my laptop.

Thanks all the same!

@chrihuc
Copy link

chrihuc commented Mar 7, 2022

btw. I had the SSL directives only shortly active, just for testing. Deactivated them directly after they did not improve the situation.

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