Raspberry Pi Projects

Project 3 — Building a Home Media Server with Jellyfin (or Plex)

What you will build A self-hosted media server that streams your films, TV shows, and music to any device at home
Difficulty Beginner — Intermediate
Time to complete 1 – 2 hours
Recommended hardware Raspberry Pi 4 (4 GB RAM or more) — transcoding is CPU-heavy

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:

Jellyfin Recommended
  • 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
Plex
  • 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 ModelDirect Play (1080p)Transcode (1080p)Transcode (4K)
Pi 4 (4 GB)Excellent1–2 streamsNot recommended
Pi 5Excellent2–3 streams1 stream (borderline)
Pi 3B+WorksStrugglesNo
Pi Zero 2WLight content onlyNoNo
The key to a smooth experience: ensure your media files use H.264 video and AAC/MP3 audio — the most widely supported formats. Files in these formats will direct play on virtually every client (phones, smart TVs, browsers) with no transcoding needed.

What You Will Need

🍓 Raspberry Pi 4 or 5 4 GB RAM minimum recommended. Pi 4 2 GB is usable for direct-play-only setups.
💾 External USB storage An SD card is too slow and too small for media. A USB hard drive or SSD is essential.
🔌 Wired Ethernet Streaming uncompressed 1080p uses ~40 Mbps. A wired connection prevents buffering.
Good power supply USB hard drives can draw significant power. Use the official Pi power supply and a powered USB hub if needed.
🎬 Your media files Films, TV shows, music — already on your hard drive. Jellyfin only streams files you own.
💻 SSH access All setup is done via the terminal. Once running, you manage Jellyfin through a web browser.

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
The 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:

Recommended media folder structure
📁/media/mediadrive/Movies/The Godfather (1972)/✓ correct
🎬The Godfather (1972).mkv
📁/media/mediadrive/TVShows/Breaking Bad/Season 01/✓ correct
📺Breaking Bad S01E01 - Pilot.mkv
📁/media/mediadrive/Movies/✗ all films dumped flat with no subfolders

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 typeFolder pathNotes
Movies/media/mediadrive/MoviesJellyfin scans subfolders automatically
TV Shows/media/mediadrive/TVShowsRequires Season XX subfolders for correct episode matching
Music/media/mediadrive/MusicArtist → 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.

Watch the scan progress in Administration → Dashboard → Activity Log. If a film or episode isn't matched correctly you can fix it by right-clicking it in the library and choosing Identify to search for the correct entry manually.

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.

PlatformAppNotes
Web browserBuilt-in — no install neededBest for initial setup and management
AndroidJellyfin for Android (Google Play / F-Droid)Free, works well
iPhone / iPadInfuse 7 or SwiftfinSwiftfin is free and open source; Infuse has a paid tier but excellent quality
Android TV / Fire TVJellyfin for Android TV (Play Store)Free, very good TV interface
Apple TVSwiftfin (TestFlight / App Store)Free and open source
RokuJellyfin for Roku (Channel Store)Free
Samsung / LG Smart TVUse the built-in browser or a streaming stickNative apps not available for all models
Windows / Mac / LinuxJellyfin 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.

Exposing Jellyfin directly to the internet means it is reachable by anyone. Make sure you have a strong admin password and consider enabling HTTPS (see below).

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.

Keep an eye on Plex downloads — the URL above will go out of date. Always grab the latest ARM64 .deb link from plex.tv/media-server-downloads.

Troubleshooting Common Problems

⚠ Jellyfin can't see my media files
This is almost always a permissions problem. Verify: (1) the drive is mounted — 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.
⚠ Video buffers constantly or won't play
Jellyfin is probably transcoding. Check what's happening in Administration → Dashboard → Active Streams — look for "Transcoding" next to the stream. Solutions: (1) Convert your media to H.264/AAC using HandBrake so it can direct play. (2) In Jellyfin playback settings on the client, set video quality to "Original" to force direct play. (3) Lower the transcode quality in Administration → Playback. (4) Use wired Ethernet rather than Wi-Fi.
⚠ A film or TV episode has wrong artwork / wrong metadata
The filename doesn't match what TMDB/TVDB expects. Right-click the item in the Jellyfin library and choose Identify to search manually. For future files, follow the naming convention exactly: Movie Title (Year).mkv for films and Show Name S01E01.mkv for episodes.
⚠ External drive not mounted after reboot
Check your /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.
⚠ Jellyfin is very slow / web interface is unresponsive
Run 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.
⚠ Can't access Jellyfin from outside my network
Work through the checklist: (1) Port 8096 is forwarded on your router to the Pi's local IP. (2) Your public IP / DDNS domain is correct — test with 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.
⚠ USB hard drive loses power / disconnects randomly
The Pi's USB ports may not supply enough current, especially for a 3.5-inch spinning hard drive. Use a powered USB hub between the Pi and the drive. Also add 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

TaskCommand / URL
Start Jellyfinsudo systemctl start jellyfin
Stop Jellyfinsudo systemctl stop jellyfin
Restart Jellyfinsudo systemctl restart jellyfin
View Jellyfin logssudo journalctl -u jellyfin -f
Check disk usagedf -h /media/mediadrive
Check what's mountedmount | grep media
Test fstab without rebootingsudo mount -a
Find drive UUIDsudo blkid
Update Jellyfinsudo apt update && sudo apt upgrade -y
Web interface (local)http://<pi-ip>:8096
Admin dashboardhttp://<pi-ip>:8096/web/index.html#!/dashboard
Jellyfin config folder/etc/jellyfin/
Jellyfin data folder/var/lib/jellyfin/
Jellyfin log folder/var/log/jellyfin/