I wanted to host Jellyfin on my home computer to be accessible to the world wide web. The internet said it was easy, but I’m picky and dumb so I found it difficult.

Reverse Proxy?

When initially researching how to do this, everyone seemed to be using apache or nginx to be the external web server, proxying traffic from Jellyfin. Totes makes sense for people with vast homelab setups and or weird setups I thought, but I don’t want or need any load balancing, just forwarding the port for me please.

This desire was cemented once I read on to see people’s reason’s for running two web servers in a conga line:

Cos security, you need it for HTTPS instead of HTPP [sic] and exposing port 8096 is bad since all the hackers live there. Defense-in-depth! People would have to hack apache to get through to the sweet sweet jelly inside.

  • reddit hive mind

Excuse me what the fuck? Jellyfin uses .NET’s built in web server Kestrel, which supports HTTPS and changing the bloody port to 443. Granted Kestrel being a Microsoft creation probably will have vulnerabilities eventually, but the WAY MORE LIKELY problem is with Jellyfin’s web app logic. Nginx passing on requests isn’t going to stop a Jellyfin specific vulnerability from being passed on.

Okay so at this point running a web proxy is off the table purely for emotional, personal taste reasons. I’m gonna be running this jelly raw on the net. This does mean the official documentation is going to be less useful and I’ll have to figure stuff out on my own.

Certs

I need a cert for TLS, and let’s encrypt with certbot is known for being excellent. First snag; Jellyfin/Kestrel requires the cert/key to be in PKCS 12 format. This is a weirdo Microsoft format that no one uses but them. Lucky some saint made a certbot plugin that makes it output this format. It’s not 100% clear at all how to use from the help output:

$ certbot -h pkcs12
...
pkcs12:
  PKCS#12 installer plugin.

  --pkcs12-location PKCS12_LOCATION
                        Location of PKCS#12 archive. (default: None)
  --pkcs12-passphrase PKCS12_PASSPHRASE
                        PKCS#12 archive passphrase. (default: None)

I didn’t have luck at first when running

sudo certbot certonly --standalone --pkcs12-location /home/jellyfin.ptx

Turns out this is an ‘installer’ plugin, and telling certbot certonly makes it skip the install step, cos you know, you said cert only. Running;

sudo certbot install -i pkcs12 --pkcs12-location /home/jellyfin.ptx

Made it spit out the PKCS12 file, and using the renew command in crontab will hopefully install when needed too.

I’m using --standalone here since I’m going to have Jellyfin on port 443/HTTPS, leaving port 80 free for certbot to do it’s temporary HTTP server domain checking thing when needed. I’m happy leaving HTTP broken and not redirecting since I want to use a very particular URL…

The URL

Jellyfin makes the homepage (/) redirect to /web/index.html, with a nice login page for people. This page unfortunately makes it pretty clear that this server is running Jellyfin, something I don’t want to be obvious to any passers-by. Lucky for me there’s an option for a Base URL so you can have https://website.com/secretjellyfinfoldername/web/index.html.

Now by secretjellyfinfoldername I mean a 50 character random string. This is where people shout ‘security through obscurity is bad security’. Well no shit, but I WANT obscurity! Is obscurity good obscurity? Yes, yes it is. Ain’t nobody going to be finding this Jellyfin instance even if they run a web content scanner.

So I set the base URL and what do you know, the homepage / redirects to my secret subfolder! In fact anything and everything does. You can visit https://website.com/uwuWhatsThis and be directed to the jellyfin loginpage at https://website.com/a1b2c3looong/web/index.html

the Jellyfin web server will automatically handle redirects to avoid displaying users invalid pages.

Wow so helpful! So completely defeating the purpose of the feature! As it functions I have no idea what situation this would be helpful for, but it’s really messing up my ‘secret URL hiding place’ plan.

This helpful feature is hardcoded as a web server middleware in the source code Jellyfin.Server/Middleware/BaseUrlRedirectionMiddleware.cs. At least this is open source software so we can just change it and recompile… Oh shit compiling jellyfin is a bit of a chore. I ignorantly blame .NET once again.

So we’re going to do something that’s somehow easier. Using dnSpy open up /usr/lib/jellyfin/bin/jellfin.dll. (Isn’t it gross seeing a unix path end with a dll file?). Find the part of the server startup code that adds the redirection middleware, app.UseBaseUrlRedirection().

Screenshot showing location of the app.UseBaseUrtRedirection() function call in jellyfin/jellyfin.dll/Jellyfin.Server/Startup/Configure
There's the fucker

Right click and Edit IL Instructions.... The line will already be highlighted/selected, just right click and replace with NOPs.

Screenshot showing replace with NOPs menu
Yeet that mofo out of there (replace with NOPs)

Save the file and chuck her back in the bin folder. Now Jellyfin won’t redirect every request it doesn’t understand and just respond with 404s, as God intended.

Port 443

This was the dumbest issue. Set the HTTPS port to 443 restart and Jellyfin will crash. I didn’t think it was that weird of an ask but apparently everyone really does believe that Jellyfin shouldn’t dare act like a ‘real’ web server.

I honestly thought the server ran as root since just about every file it touched was owned by root, but apparently not. So we’re dealing with the old ports under 1024 need to run as root on unix cos ‘security’. This is from the flippin mainframe days where hundreds of cheeky college students on the same computer might run their own naughty telnet service that’s mistaken for the real admin controlled one.

In the context of a server with a single user running exposed services, this rule is stupid as fuck. I have no idea why it’s still a thing, but unix admins really don’t wanna give up control over ports 1-1024.

 sudo sysctl -w net.ipv4.ip_unprivileged_port_start=0

Will remove the limitation until the next reboot, apparently running

sudo echo 'net.ipv4.ip_unprivileged_port_start=0' > /etc/sysctl.d/99-rootless.conf

Will make the setting permanent, but I just get Permission denied on my system. They really don’t want to let go of ports 1-1024 do they.

Jebus that was hard

I know right? All these extra annoyances can also be avoided by using apache or nginx as a reverse proxy, then you can fully control how the web server functions with a config file and have a more normie setup that works out of the box better. I imagine this is the real reason it’s a popular setup, because Jellyfin/Kestrel sucks balls.

Why can’t they just tell you that up front? Why pretend this whole reverse proxy thing is for security theatre when it’s really just to address Jellyfin’s shortcomings? Even weirder, the github readme explains that running jellyfin with Kestrel disabled and instead using your own webserver to host the files is totally doable.

A very dumbfounded white persian guardian taxidermy