Raspberry Pi Projects
Project 3 — Building a Home Media Server with Jellyfin (or Plex)
Jellyfin or Plex?
Both are excellent media server applications that organise your personal collection and stream it to any device. They look and feel similar but have important differences:
- 100% free and open source — no account required
- No monthly subscription — ever
- No features locked behind a paywall
- Your data stays on your hardware
- Active community and rapid development
- Slightly less polished UI than Plex
- Mobile apps are free but less refined
- Very polished, Netflix-like interface
- Excellent mobile and TV apps
- Large established user base
- Free tier has limited features (offline sync, mobile playback locked)
- Plex Pass subscription: ~£4/month or £120 lifetime
- Requires a Plex account and internet connection to set up
- Media metadata is sent to Plex servers
This project uses Jellyfin — it is free in every sense of the word and works brilliantly on a Raspberry Pi. Instructions for Plex are included at the end.
A Note on Transcoding
A media server can deliver video to a client in two ways:
- Direct play / Direct stream: the client plays the file as-is. The Pi barely uses any CPU. This works when the client supports the video codec and container natively.
- Transcoding: the Pi converts the video in real time to a format the client can play. This is very CPU-intensive.
| Pi Model | Direct Play (1080p) | Transcode (1080p) | Transcode (4K) |
|---|---|---|---|
| Pi 4 (4 GB) | Excellent | 1–2 streams | Not recommended |
| Pi 5 | Excellent | 2–3 streams | 1 stream (borderline) |
| Pi 3B+ | Works | Struggles | No |
| Pi Zero 2W | Light content only | No | No |
What You Will Need
Step 1 — Mount Your External Drive
Plug your USB hard drive into the Pi. Find the device name:
pi@raspberrypi:~$ lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS sda 8:0 0 2.0T 0 disk └─sda1 8:1 0 2.0T 0 part mmcblk0 179:0 0 32.0G 0 disk ├─mmcblk0p1 179:1 0 512.0M 0 part /boot/firmware └─mmcblk0p2 179:2 0 31.5G 0 part /
The external drive is sda with one partition sda1. Create a mount point and mount it:
pi@raspberrypi:~$ sudo mkdir -p /media/mediadrive pi@raspberrypi:~$ sudo mount /dev/sda1 /media/mediadrive pi@raspberrypi:~$ ls /media/mediadrive Movies TVShows Music
Mount automatically on boot
Get the drive's UUID (more reliable than /dev/sda1, which can change):
pi@raspberrypi:~$ sudo blkid /dev/sda1 /dev/sda1: UUID="a1b2c3d4-e5f6-7890-abcd-ef1234567890" TYPE="ext4" PARTUUID="..."
Add an entry to /etc/fstab:
pi@raspberrypi:~$ sudo nano /etc/fstab
Add this line at the bottom (replace the UUID with yours):
UUID=a1b2c3d4-e5f6-7890-abcd-ef1234567890 /media/mediadrive ext4 defaults,nofail 0 2
nofail option is important — it means the Pi will still boot normally even if the drive isn't connected. Without it, a missing drive can leave your Pi in an emergency mode on startup.
Test that the fstab entry is correct before rebooting:
pi@raspberrypi:~$ sudo mount -a # No output = success. If you see an error, fix the fstab entry before rebooting.
Organise your media
Jellyfin (like Plex) relies on a consistent folder and filename structure to look up metadata (cover art, descriptions, cast). The expected layout:
Step 2 — Install Jellyfin
Jellyfin provides an official repository for Debian/Raspberry Pi OS. Add it and install:
pi@raspberrypi:~$ curl -fsSL https://repo.jellyfin.org/install-debuntu.sh | sudo bash Jellyfin installation script for Debian/Ubuntu ... Detected OS: Debian GNU/Linux 12 (bookworm) Detected arch: arm64 Adding Jellyfin repository... Installing Jellyfin... Jellyfin is installed and running!
Verify the service is running:
pi@raspberrypi:~$ sudo systemctl status jellyfin ● jellyfin.service - Jellyfin Media Server Loaded: loaded (/lib/systemd/system/jellyfin.service; enabled) Active: active (running) since Thu 2026-06-11 09:45:12 BST; 30s ago
Jellyfin runs on port 8096 by default. Make sure it starts on boot:
pi@raspberrypi:~$ sudo systemctl enable jellyfin
Step 3 — Initial Setup Wizard
Open a browser on any device on your network and go to:
http://192.168.1.xx:8096 # replace with your Pi's IP address
The first-run wizard will guide you through the following steps:
- Select your language — choose your preferred display language.
- Create an admin account — set a username and strong password. This is your Jellyfin administrator login.
- Add your media libraries — click Add Media Library for each type of content you have. See Step 4 for details.
- Metadata language and country — set these so artwork and episode guides are downloaded in the correct language.
- Remote access — leave enabled if you want to access Jellyfin from outside your home.
Click Finish and log in with the admin account you just created.
Step 4 — Add Media Libraries
In the Jellyfin dashboard go to Administration → Dashboard → Libraries → Add Media Library. Add one library for each content type:
| Content type | Folder path | Notes |
|---|---|---|
| Movies | /media/mediadrive/Movies | Jellyfin scans subfolders automatically |
| TV Shows | /media/mediadrive/TVShows | Requires Season XX subfolders for correct episode matching |
| Music | /media/mediadrive/Music | Artist → Album → Track structure recommended |
After adding libraries, Jellyfin will scan your folders and download metadata (posters, descriptions, ratings) from The Movie Database (TMDB) and TheTVDB. This can take several minutes for a large collection.
Step 5 — Give Jellyfin Access to Your Media
Jellyfin runs as the jellyfin system user, which may not have permission to read your drive. Fix this by adding Jellyfin to a group that has access, or by adjusting drive permissions:
pi@raspberrypi:~$ sudo usermod -aG pi jellyfin # add jellyfin user to the pi group (which owns the mounted drive) pi@raspberrypi:~$ sudo systemctl restart jellyfin
Alternatively, set permissions directly on your media folders:
pi@raspberrypi:~$ sudo chown -R jellyfin:jellyfin /media/mediadrive pi@raspberrypi:~$ sudo chmod -R 755 /media/mediadrive
Step 6 — Install Jellyfin Clients
Jellyfin has apps for virtually every platform. You do not need to install anything to watch on a computer — just use the web interface at http://pi-ip:8096.
| Platform | App | Notes |
|---|---|---|
| Web browser | Built-in — no install needed | Best for initial setup and management |
| Android | Jellyfin for Android (Google Play / F-Droid) | Free, works well |
| iPhone / iPad | Infuse 7 or Swiftfin | Swiftfin is free and open source; Infuse has a paid tier but excellent quality |
| Android TV / Fire TV | Jellyfin for Android TV (Play Store) | Free, very good TV interface |
| Apple TV | Swiftfin (TestFlight / App Store) | Free and open source |
| Roku | Jellyfin for Roku (Channel Store) | Free |
| Samsung / LG Smart TV | Use the built-in browser or a streaming stick | Native apps not available for all models |
| Windows / Mac / Linux | Jellyfin Media Player (desktop app) | Better performance than browser for local playback |
Step 7 — Remote Access (Streaming Outside Your Home)
To watch your media when away from home, you need to make Jellyfin reachable from the internet.
Option A — Port forwarding (simplest)
Forward port 8096 (or 8920 for HTTPS) from your router to your Pi's local IP. Then access Jellyfin at http://your-public-ip:8096 or use a DDNS service with a domain name.
Option B — Reverse proxy with SSL (recommended)
If you completed Project 1 and already have Apache or Nginx running with a domain and SSL certificate, you can put Jellyfin behind that proxy so it serves over HTTPS at https://jellyfin.yourdomain.com.
Add this to your Apache config (requires mod_proxy and mod_proxy_http):
pi@raspberrypi:~$ sudo a2enmod proxy proxy_http proxy_wstunnel headers # In /etc/apache2/sites-available/jellyfin.conf: <VirtualHost *:443> ServerName jellyfin.yourdomain.com SSLEngine on SSLCertificateFile /etc/letsencrypt/live/yourdomain.com/fullchain.pem SSLCertificateKeyFile /etc/letsencrypt/live/yourdomain.com/privkey.pem ProxyPass / http://localhost:8096/ ProxyPassReverse / http://localhost:8096/ ProxyPreserveHost On # Required for Jellyfin WebSocket connections RewriteEngine On RewriteCond %{HTTP:Upgrade} =websocket [NC] RewriteRule /(.*) ws://localhost:8096/$1 [P,L] </VirtualHost>
Option C — Tailscale VPN (most secure)
Install Tailscale on both your Pi and your phone/laptop. Access Jellyfin using the Pi's Tailscale IP — no port forwarding needed, no exposure to the internet.
pi@raspberrypi:~$ curl -fsSL https://tailscale.com/install.sh | sh pi@raspberrypi:~$ sudo tailscale up To authenticate, visit: https://login.tailscale.com/a/xxxxxxxx
Step 8 — Keeping Jellyfin Updated
Because Jellyfin is installed from its own repository, updates arrive via the normal system update mechanism:
pi@raspberrypi:~$ sudo apt update && sudo apt upgrade -y
Check your current version in Administration → Dashboard → About.
Alternative: Installing Plex Media Server
If you prefer Plex, here is how to install it. You will need a free Plex account at plex.tv.
# Download the latest ARMv8 .deb package from plex.tv/media-server-downloads pi@raspberrypi:~$ wget "https://downloads.plex.tv/plex-media-server-new/1.41.0.9994-f2c27da23/debian/plexmediaserver_1.41.0.9994-f2c27da23_arm64.deb" pi@raspberrypi:~$ sudo dpkg -i plexmediaserver_*.deb pi@raspberrypi:~$ sudo systemctl enable --now plexmediaserver
Then access the Plex setup wizard from a browser on the same machine as the Pi, or via SSH tunnel:
# On your laptop — tunnel Plex port through SSH: you@laptop:~$ ssh -L 32400:localhost:32400 pi@192.168.1.xx
Then open http://localhost:32400/web in your browser to complete setup.
.deb link from plex.tv/media-server-downloads.
Troubleshooting Common Problems
ls /media/mediadrive should show your folders. (2) the jellyfin user can read the files: sudo -u jellyfin ls /media/mediadrive/Movies. If that fails, run sudo usermod -aG pi jellyfin and restart Jellyfin. (3) Check the scan log in Administration → Dashboard → Activity Log for specific errors.Movie Title (Year).mkv for films and Show Name S01E01.mkv for episodes./etc/fstab entry for errors: sudo mount -a will report any problems. Make sure you used the UUID (not /dev/sda1) and included the nofail option. Also verify the filesystem type matches — use sudo blkid to see the actual type (ext4, ntfs, exfat, etc.). NTFS and exFAT drives need extra packages: sudo apt install ntfs-3g exfatprogs.htop and check if a ffmpeg process is pegging the CPU — that means transcoding is happening. Also check available RAM: Jellyfin's metadata database can grow large. If the Pi has less than 1 GB free RAM, restart Jellyfin (sudo systemctl restart jellyfin) to clear its cache. Consider splitting very large libraries into multiple scan passes.curl api.ipify.org from the Pi and compare to what your domain resolves to. (3) In Jellyfin Settings → Networking, ensure "Allow remote connections" is ticked. (4) The Pi's firewall (if any) allows port 8096: sudo ufw allow 8096.max_usb_current=1 to /boot/firmware/config.txt (Pi 4) or disable USB selective suspend: echo -1 | sudo tee /sys/module/usbcore/parameters/autosuspend to persist across reboots, add that to /etc/rc.local.Quick Reference
| Task | Command / URL |
|---|---|
| Start Jellyfin | sudo systemctl start jellyfin |
| Stop Jellyfin | sudo systemctl stop jellyfin |
| Restart Jellyfin | sudo systemctl restart jellyfin |
| View Jellyfin logs | sudo journalctl -u jellyfin -f |
| Check disk usage | df -h /media/mediadrive |
| Check what's mounted | mount | grep media |
| Test fstab without rebooting | sudo mount -a |
| Find drive UUID | sudo blkid |
| Update Jellyfin | sudo apt update && sudo apt upgrade -y |
| Web interface (local) | http://<pi-ip>:8096 |
| Admin dashboard | http://<pi-ip>:8096/web/index.html#!/dashboard |
| Jellyfin config folder | /etc/jellyfin/ |
| Jellyfin data folder | /var/lib/jellyfin/ |
| Jellyfin log folder | /var/log/jellyfin/ |