Nginx Reverse Proxy for Subdomain

My setup

  • nginx 1.19.7
  • Plex server versions 1.21.3.4046 and 1.21.3.4021 (or higher, maybe lower)
  • Ubuntu 20.04 LTS

Requirements

  • nginx >1.17.0
  • nginx modules: --with-debug --with-cc-opt=-Wimplicit-fallthrough=0 --with-pcre=objs/lib/pcre-8.44 --with-zlib=objs/lib/zlib-1.2.11 --with-openssl-opt='no-asm no-tests' --with-file-aio --with-http_addition_module --with-http_auth_request_module --with-http_dav_module --with-http_degradation_module --with-http_flv_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_image_filter_module --with-http_mp4_module --with-http_random_index_module --with-http_realip_module --with-http_secure_link_module --with-http_slice_module --with-http_ssl_module --with-http_stub_status_module --with-http_sub_module --with-http_v2_module --with-http_xslt_module --with-mail --with-mail_ssl_module --with-poll_module --with-select_module --with-stream --with-stream_ssl_module --with-stream_ssl_preread_module

A couple things worth mentioning:

  • Only use TLSv1.2 and TLSv1.3
  • Change ciphers accordingly
  • You don’t need the error_page section if you don’t want (but will tell you if Plex server isn’t running for some reason or another)
  • Add some security headers
  • Turn on SSL Session Cache
  • SSL Stapling (if don’t have a self-signed certificate)
  • Use upstream if you want (required nginx upstream module)

What this config enables on Plex

  • Can watch Live TV, Movies & Shows on Plex
  • Plays TV Show theme songs
  • Plays Movie trailers, extras, and featurettes

Finally

If you run into problems with images not displaying, media not playing, or something else that worked before, comment out the add_header Content-Security-Policy line and reload nginx and see if that helps.

plex.conf

upstream plex {
    server localhost:32400;
}
server {
    listen 443 ssl http2;
    server_name plex.domain.com;
    ssl_certificate /etc/nginx/ssl/cert.pem;
    ssl_certificate_key /etc/nginx/ssl/key.key;
    client_max_body_size 500M;
    send_timeout 100m;
    
    ssl_session_cache builtin:1000 shared:SSL:10m;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers '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';
    ssl_stapling on;
    ssl_stapling_verify on;
    # For LetsEncrypt/Certbot, you can get your chain like this: https://esham.io/2016/01/ocsp-stapling
    ssl_trusted_certificate /path/to/intermediate/ocsp/cert-r3.pem;
    
    add_header Strict-Transport-Security max-age=15768000;
    add_header Referrer-Policy strict-origin-when-cross-origin;
    add_header X-Frame-Options deny;
    add_header X-Content-Type-Options nosniff;
    add_header X-XSS-Protection "1; mode=block";
    add_header Permissions-Policy "geolocation=(self), midi=(self), sync-xhr=(self), microphone=(self), camera=(self), magnetometer=(self), gyroscope=(self), fullscreen=(self), payment=(self)";
    # Pay attention to how many domains we need to allow
    add_header Content-Security-Policy "default-src 'none'; base-uri 'self' plex.domain.com; font-src 'self' data: plex.domain.com; media-src 'self' data: blob: plex.domain.com https://*.plex.direct:32400 https://video.internetvideoarchive.net https://*.cloudfront.net; script-src 'self' 'unsafe-inline' 'unsafe-eval' domain.com plex.domain.com; style-src 'self' 'unsafe-inline' plex.domain.com; img-src 'self' data: blob: https: plex.domain.com; worker-src * blob:; frame-src 'self'; connect-src 'self' https: domain.com plex.domain.com wss://*.plex.direct:32400 wss://pubsub.plex.tv; object-src 'self' plex.domain.com; frame-ancestors 'self' domain.com plex.domain.com; form-action 'self' plex.domain.com; manifest-src 'self' plex.domain.com; script-src-elem 'self' 'unsafe-inline' domain.com plex.domain.com www.gstatic.com";

    error_page 500 502 503 504 /50x.html;
        location = /50x.html {
        root /path/to/503;
    }
    
    # gzip source: https://github.com/toomuchio/plex-nginx-reverseproxy/blob/master/nginx.conf
    # Plex has A LOT of javascript, xml and html. This helps a lot, but if it causes playback issues with devices, disable this section
    gzip on;
    gzip_vary on;
    gzip_min_length 1000;
    gzip_proxied any;
    gzip_types text/plain text/css text/xml application/xml text/javascript application/x-javascript image/svg+xml;
    gzip_disable "MSIE [1-6]\.";

    # Forward real ip and host to Plex
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
	
    # If not using ngx_http_realip_module change '$http_x_forwarded_for,$realip_remote_addr' to $proxy_add_x_forwarded_for
    proxy_set_header X-Forwarded-For '$proxy_add_x_forwarded_for,$realip_remote_addr';
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header Sec-WebSocket-Extensions $http_sec_websocket_extensions;
    proxy_set_header Sec-WebSocket-Key $http_sec_websocket_key;
    proxy_set_header Sec-WebSocket-Version $http_sec_websocket_version;

    # Disables compression between Plex and Nginx, required if using sub_filter below.
    # May also improve loading time by a very marginal amount, as nginx will compress anyway.
    #proxy_set_header Accept-Encoding "";

    # Buffering off send to the client as soon as the data is received from Plex.
    proxy_redirect off;
    proxy_buffering off;

    location / {
        proxy_pass http://plex/;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_ssl_verify off;
        proxy_http_version 1.1;
        proxy_set_header Host $http_host;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_read_timeout 86400;
    }
}

Need Plex for a URL subpath?

Change to this:

location /plex/ {
        proxy_pass http://plex/;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_ssl_verify off;
        proxy_http_version 1.1;
        proxy_set_header Host $http_host;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_read_timeout 86400;
    }

Other notes

  • This reverse proxy, but for apache is here.
  • Using nginx with Plex is lightening fast.
  • Have more than one Plex server? Try this for some load balancing:
upstream plex {
    server localhost:32400 weight=5;
    server 192.168.1.22:32400 max_conns=2;
}

Neat. Does this work with most clients, do you know?

Why do you set this? Image/video uploads, I’m guessing?

And that? That’s so long.

And this one. I’d have guessed this would be harmful.

As far as I know, this should work with lot’s of modern clients because of the newer security protocols.

I do user Plex for video uploads from my iPhone. A 60 second 1080p 60fps video is approximately 100MB. This is the reason my client_max_body_size is set so high. You may adjust this to something way more reasonable if you do not use that functionality for Plex.

“Some players don’t reopen a socket and playback stops totally instead of resuming after an extended pause i.e. Chrome”. This was taken directly from this source. So, if I interpret that correctly, if you watch a two hour movie, in theory, it might stop playing.

The proxy_buffering set to off tells nginx to immediately send the data to and from Plex rather than adding overhead between the client and the server.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.