Nginx reverse proxy for subdomain only slow for Jellyfin

Hi all,

I’ve posted around a few places to try get help but haven’t found anyone who is willing as of yet. I think I need help from someone with nginx reverse proxy experience.

I’ve already wrote up a issue here:

So I won’t bother repeating myself. I’ve done as much troubleshooting as I can and capture what I thought to be the most relevant information in the github issue above.

Is the site just slow or is the streaming playback not working?

Unless your server name is really jellyfin.mywebsite.com, try updating all of the server_name values with the correct name.

Why do you have the following commented out?

#    proxy_set_header Host      $host;
#    proxy_set_header X-Real-IP $remote_addr;
1 Like

They’ve been defined earlier in the config.

It’s as if someone forgot to run a bundler on that web app… – there’s a gazillion tiny .js files being served.

Alright, so I just spent the time to recreate the entire config for you:

I wasn’t able to recreate your issue (checkout to /opt, update docker-compose.yml with your media paths, and then point to your docker host with a host file)

Also, i know you didnt ask, but i was able to do pretty much the same thing with plex without any effort:

docker create --name=plex --net=host -e PUID=1000 -e PGID=1000 -e UMASK_SET=022 -v /nas/plex_config:/config -v /nas/TVShows:/tv -v /nas/Anime:/anime -v /nas/Movies:/movies --restart unless-stopped linuxserver/plex && \
docker start plex

Yeah lol it is still WIP.

I thought about hacking around with it with webpack.

There are a lot of optimizations that could be done with transpilation.

Here is my working nginx reverse proxy config.

I do not experience the issues OP is describing, however, I do not use containers I use a dedicated virtual machine.

server {
    server_name TV.EXAMPLE.COM;
    listen 443 ssl http2; # managed by Certbot
    
    access_log /var/log/nginx/TV.EXAMPLE.COM.access.log;
    error_log /var/log/nginx/TV.EXAMPLE.COM.error.log;
            
    ssl_certificate /etc/letsencrypt/live/TV.EXAMPLE.COM/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/TV.EXAMPLE.COM/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
    ssl_trusted_certificate /etc/letsencrypt/live/TV.EXAMPLE.COM/chain.pem;
    ssl_stapling on;
    ssl_stapling_verify on;
    add_header Strict-Transport-Security "max-age=31536000" always;

    # Security / XSS Mitigation Headers
    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-XSS-Protection "1; mode=block";
    add_header X-Content-Type-Options "nosniff";

    # Content Security Policy
    # See: https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
    # Enforces https content and restricts JS/CSS to origin
    # External Javascript (such as cast_sender.js for Chromecast or YouTube embed JS for external trailers) must be whitelisted.
    add_header Content-Security-Policy "default-src https: data: blob:; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline' https://www.gstatic.com/cv/js/sender/v1/cast_sender.js https://www.youtube.com/iframe_api https://s.ytimg.com; worker-src 'self' blob:; connect-src 'self'; object-src 'none'; frame-ancestors 'self'";
    
    location ~ /.well-known {
        allow all;
    }
    location / {
        # Proxy main Jellyfin traffic
        proxy_pass http://192.168.1.54:8096/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Protocol $scheme;
        proxy_set_header X-Forwarded-Host $http_host;

        # Disable buffering when the nginx proxy gets very resource heavy upon streaming
        proxy_buffering off;
    }
    location /socket {
        # Proxy Jellyfin Websockets traffic
        proxy_pass http://192.168.1.54:8096/socket;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Protocol $scheme;
        proxy_set_header X-Forwarded-Host $http_host;
    }
}
server {
    if ($host = TV.EXAMPLE.COM) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


    listen 80;
    server_name TV.EXAMPLE.COM;
    
    return 404; # managed by Certbot
}

Also, don’t forget to set this setting in the jellyfin admin.

2 Likes

FWIW I don’t have a problem running jellyfin behind an nginx proxy, but I also don’t have the /socket path specifically mentioned in my config. I can play around with this a bit more later though

Since you don’t define the socket path it could result in long polling which might explain the poor performance.

I’m sorry, but I still don’t understand… If they both point to the same place, what’s the big deal? Is it just the extra options?

Web sockets are different than polling.

1 Like

To expound on this:

I mean that still doesn’t explain why you need to specifically define the socket path when it already matches the first location. The only difference between the two locations I see is with proxy buffering? Does having a second location let nginx handle the connections with a second worker?

Because the protocol is entirely different.

No I think it comes down to setting specific headers to get sockets to work on that specific subpath which would(could?) otherwise interfere with the main server block.

It’s probably worth asking the devs about why they had to do that.

But we can debate the usefulness on this later: what I’m more interested in is if adding that to your config solved your issue.

Ohhh that’s where the ‘Upgrade’ and ‘Connection’ bits come in? I think I missed that. But couldn’t you just throw the upgrade and connection blocks into the main location again and just be done with it?

I’m not that sure, honestly. I know about Web Sockets. I know about Nginx proxying.

I do not know about nginx proxying web sockets lol

Might honestly be worth a try. :man_shrugging:

I mean that’s what I have in mine and it works, but I haven’t done any kind of performance testing on it, nor do I really know how… Something to look into though.