Jellyfin Proxy
Running a Jellyfin Proxy with NixOS, a Raspberry Pi, and Tailscale
Problem
I want to access my Jellyfin server from anywhere. Here is how I achieved this using NixOS, a Raspberry Pi (RPi), and Tailscale.
Background
At home, I run a NAS with Jellyfin to watch my own media. Previously, I had used Plex, but I decided to make the change to Jellyfin after a few frustrations.
Plex had a service for easily connecting to your home server from anywhere called “Plex Remote Access,” which I often used while traveling and was a must-have for my Jellyfin setup.
This was also an opportune time to harden the security of my NAS. Instead of opening my server to the world, I wanted to use Wireguard as the only remote access to my NAS.
Tailscale seemed like a great fit, as I had already replaced Synology’s QuickConnect service with Tailscale + SSH.
Setting up the Raspberry Pi (RPi)
After ordering an RPi Model 4 B, I decided to run NixOS instead of Raspbian. I use an RPi because they are relatively inexpensive and easy to work with. Eventually, I’d like to find a smaller device or microcontroller for this project, but this serves as a great start.
I chose NixOS because:
- I run NixOS on my desktop and laptop, so I can share a base config across my devices.
- The Raspberry Pi is replaceable and scalable. Replacement is as easy as flashing a new SD card.
- All of the device is defined as code.
- I can track changes in source control!
- The contents on the RPi are discoverable.
- In a low-touch project like this, it’s hard to remember the changes I make, so a mutable system would lead to natural drift.
Defining the RPi NixOS Flake
I extended my existing NixOS flake to include a definition for the RPi that will run my Jellyfin proxy.
rpi = nixpkgs.lib.nixosSystem {
system = "aarch64-linux"; # RPi is a 64-bit ARM system
specialArgs = {
hostname = "rpi"; # Pass the host name to rpi.nix. This lets us create multiple RPi proxy devices.
};
modules = [
./systems/rpi/rpi.nix # RPi specific configurations.
..
];
};
You can find the complete flake on my GitHub.
RPi Nix Config
With the RPi added as a target to the flake, I defined the RPi config. Here are the highlights:
# Define a Systemd service to open an SSH tunnel to the NAS.
# This allows other devices to connect to Jellyfin through the RPi.
systemd.services.jellyfin = {
enable = true;
description = "Jellyfin SSH gateway";
after = [ "sshd.target" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "simple";
ExecStart = "${pkgs.openssh}/bin/ssh -v jellyfin-proxy";
Restart = "on-failure";
RestartSec = "15s";
};
};
# Add tailscale service so RPi can connect to NAS from anywhere.
# Don't forget to add the RPi to your tailnet!
services.tailscale.enable = true;
# Allow other devices to connect to the Jellyfin port
networking.firewall = {
enable = true;
allowedTCPPorts = [
22 # SSH port
8096 # Jellyfin port
];
};
# Enable SSH. We will use a Gateway port to allow other devices to use the tunnel.
services.openssh.enable = true;
services.openssh.settings.GatewayPorts = "yes";
You can find the complete config on my GitHub.
Configuring the SSH tunnel
This is a snippet of the SSH config I use to forward the Jellyfin port.
Host jellyfin-proxy
HostName $TAILSCALE_IP_OF_JELLYFIN_MACHINE
User $YOUR_USER
LocalForward 8096 127.0.0.1:8096 # Forward Jellyfin port over SSH
SessionType none
GatewayPorts yes # Allow other devices to access Jellyfin
ServerAliveInterval 240 # Keep SSH connection alive
Conclusion
Success! I have an easy way to bridge Jellyfin to any remote network. I have successfully run this setup in multiple locations. It’s as simple as looking up the local IP address of the RPi and pointing any Jellyfin client to it.
In the future, I would like to use HTTPS for this port and further harden the Tailscale connection, but I am happy with the result!