šŸ–• Cloud Exodus Notice: All No iLLusion services are migrating off cloud platforms to a home-grown, fully encrypted server within 6-12 months. Protected by a 100-character password scattered across 10 sticky notes in different languages. Just eat one note and the whole thing goes dark.
No real names, no tracking, no overlords.

Anonymous Streaming Guide

Build your own private media streaming setup with self-hosted open source solutions

Last verified: October 2026

Self-Hosted Media Stack: October 2026

Jellyfin 12.2 (the project dropped the old "10.x" numbering with 12.0 in September 2026), Sonarr v4, Radarr v6, Prowlarr v2 and qBittorrent 5.2 give you a Netflix-style experience you fully control. Downloads run inside a gluetun VPN container so nothing leaks outside the tunnel.

Mullvad: WireGuard-only since OpenVPN was fully shut down on January 15, 2026, and no port forwarding since 2023. Great for privacy, weaker for seeding.
Need port forwarding? Proton VPN (paid plans, P2P servers) and Private Internet Access still offer it. IVPN dropped it in 2023.

Why Self-Hosted Streaming?

Self-hosting gives you complete control over your media library without the restrictions, tracking, or subscription fees of commercial platforms. Own your content, protect your privacy, and access everything from anywhere.

Complete Privacy

No viewing history sent to anyone

Total Control

Your content, your rules

Cost Effective

Hardware once, no monthly fees

Legal note: This stack is for media you have the rights to: your own rips, public domain and Creative Commons works, and content licensed for download. A VPN hides traffic from your ISP; it does not make copyright infringement legal.

Essential Media Stack Components

Jellyfin 12.2

Web UI port 8096

Free, open-source media server. No account with a company, no telemetry, hardware transcoding included free (unlike Plex Pass).

Sonarr v4

Web UI port 8989

Watches for new TV episodes, grabs them through your download client, then renames and files them into your library.

Radarr v6

Web UI port 7878

The same automation for movies: wanted list, quality profiles, custom formats and automatic upgrades.

Prowlarr v2

Web UI port 9696

One place to manage indexers. It pushes them to Sonarr and Radarr automatically so you never configure them twice.

Infrastructure & Hardware Setup

1Hardware Requirements & Recommendations

Basic Setup (1-2 Users)

  • CPU: Intel N100/N150 mini PC or similar
  • RAM: 8 GB
  • Storage: 500 GB SSD + 4 TB HDD
  • Network: Gigabit Ethernet
  • Power: ~10-15W idle

Recommended (5-10 Users)

  • CPU: Intel Core i5 (Quick Sync) or AMD Ryzen 5
  • RAM: 16 GB
  • Storage: 1 TB NVMe + 8-16 TB HDD
  • GPU: Intel iGPU handles most transcodes
  • Power: ~30-60W typical

Enthusiast (10+ Users)

  • CPU: Core i7/i9 or Ryzen 7/9
  • RAM: 32 GB+
  • Storage: 2 TB NVMe + multi-drive array
  • GPU: Intel Arc or NVIDIA for many 4K transcodes
  • Power: 100W+ under load

Storage Planning:

  • • SSD: OS, Docker, app configs and databases
  • • SSD: Jellyfin metadata and transcode cache
  • • HDD: the whole /data tree (torrents + media)
  • • Keep torrents and media on the same filesystem so hardlinks work

2Operating System Selection

Recommended: Ubuntu Server 26.04 LTS

  • • "Resolute Raccoon", released April 23, 2026
  • • Standard support until May 2031 (ESM to 2036)
  • • First-class Docker support and documentation
  • • Recent kernel for new Intel/AMD iGPU transcoding

Alternative: Debian 13 "Trixie"

  • • Rock-solid stability, minimal footprint
  • • No Snap packages or vendor extras
  • • Excellent for headless servers
  • • Same Docker install steps as Ubuntu

3Initial Server Configuration

# Update system packages
sudo apt update && sudo apt full-upgrade -y

# Essential tools
sudo apt install -y curl git htop tree unzip unattended-upgrades

# Timezone (list options with: timedatectl list-timezones)
sudo timedatectl set-timezone America/New_York

# Firewall: allow SSH BEFORE enabling or you lock yourself out
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
sudo ufw enable

# Automatic security updates
sudo dpkg-reconfigure -plow unattended-upgrades
SSH Hardening:
  • • Copy your key first: ssh-copy-id user@server
  • • Set PasswordAuthentication no
  • • Set PermitRootLogin no
  • • Restart: sudo systemctl restart ssh
Docker vs UFW:

Ports published by Docker bypass UFW rules. Only publish what you need, keep the server behind your router (no port forwards), and use the remote access options below instead of opening ports.

4Storage & Directory Layout

Use a single /data tree (the layout popularized by the TRaSH Guides). When downloads and library share one filesystem and one mount, Sonarr and Radarr can hardlink files: instant imports, no duplicate disk usage, and you keep seeding.

# One shared /data tree so hardlinks and instant moves work
sudo mkdir -p /data/torrents/{movies,tv,music}
sudo mkdir -p /data/media/{movies,tv,music}

# App configs live separately from media
sudo mkdir -p /opt/mediaserver/config/{jellyfin,sonarr,radarr,prowlarr,qbittorrent,gluetun}

# Owned by your media user (check your IDs with: id)
sudo chown -R $USER:$USER /data /opt/mediaserver
sudo chmod -R a=,a+rX,u+w,g+w /data
/data
ā”œā”€ā”€ torrents/        <- qBittorrent downloads here
│   ā”œā”€ā”€ movies/
│   ā”œā”€ā”€ tv/
│   └── music/
└── media/           <- Sonarr/Radarr hardlink finished files here
    ā”œā”€ā”€ movies/
    ā”œā”€ā”€ tv/
    └── music/

/opt/mediaserver
ā”œā”€ā”€ compose.yaml
ā”œā”€ā”€ .env
└── config/
    ā”œā”€ā”€ jellyfin/  sonarr/  radarr/
    └── prowlarr/  qbittorrent/  gluetun/
Mounting Data Drives:
  • • Format with ext4 or XFS (or ZFS/Btrfs if you want snapshots)
  • • Mount by UUID in /etc/fstab (find it with lsblk -f)
  • • Pool multiple drives with mergerfs or a RAID/ZFS array, and keep it mounted at /data
  • • RAID is not a backup: keep irreplaceable files somewhere else too

Docker Container Deployment

1Docker Engine & Compose Installation

# Remove distro-packaged Docker if present
for pkg in docker.io docker-compose docker-compose-v2 podman-docker containerd runc; do
  sudo apt-get remove -y $pkg
done

# Install Docker Engine + Compose plugin from Docker's script
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh

# Run docker without sudo (log out and back in afterwards)
sudo usermod -aG docker $USER

# Verify: Engine 29.x and Compose v5.x as of October 2026
docker --version
docker compose version
docker run --rm hello-world

Compose ships as a Docker plugin. Always type docker compose (with a space). The old standalone docker-compose binary is retired.

2Environment Variables

# /opt/mediaserver/.env  (chmod 600, never commit it)
PUID=1000
PGID=1000
TZ=America/New_York

CONFIG_ROOT=/opt/mediaserver/config
DATA_ROOT=/data

# VPN (from your provider's WireGuard config generator)
VPN_SERVICE_PROVIDER=protonvpn
WIREGUARD_PRIVATE_KEY=replace-with-your-private-key
SERVER_COUNTRIES=Switzerland

Generate the WireGuard key in your VPN provider's account dashboard. Protect the file with chmod 600 .env since it holds your VPN credentials.

3Complete compose.yaml

Save as /opt/mediaserver/compose.yaml. qBittorrent has no ports of its own: it lives inside gluetun's network, so if the VPN drops, it has no route to the internet at all.

services:
  gluetun:
    image: qmcgaw/gluetun:v3.41.3
    container_name: gluetun
    cap_add:
      - NET_ADMIN
    devices:
      - /dev/net/tun:/dev/net/tun
    ports:
      - "8080:8080"          # qBittorrent Web UI (published here, not on qbittorrent)
    volumes:
      - ${CONFIG_ROOT}/gluetun:/gluetun
    environment:
      - VPN_SERVICE_PROVIDER=${VPN_SERVICE_PROVIDER}
      - VPN_TYPE=wireguard
      - WIREGUARD_PRIVATE_KEY=${WIREGUARD_PRIVATE_KEY}
      - SERVER_COUNTRIES=${SERVER_COUNTRIES}
      - VPN_PORT_FORWARDING=on     # Proton VPN / PIA only
      - TZ=${TZ}
    restart: unless-stopped

  qbittorrent:
    image: lscr.io/linuxserver/qbittorrent:latest
    container_name: qbittorrent
    network_mode: "service:gluetun"   # all traffic goes through the VPN tunnel
    depends_on:
      gluetun:
        condition: service_healthy
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
      - WEBUI_PORT=8080
    volumes:
      - ${CONFIG_ROOT}/qbittorrent:/config
      - ${DATA_ROOT}/torrents:/data/torrents
    restart: unless-stopped

  prowlarr:
    image: lscr.io/linuxserver/prowlarr:latest
    container_name: prowlarr
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
    volumes:
      - ${CONFIG_ROOT}/prowlarr:/config
    ports:
      - "9696:9696"
    restart: unless-stopped

  sonarr:
    image: lscr.io/linuxserver/sonarr:latest
    container_name: sonarr
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
    volumes:
      - ${CONFIG_ROOT}/sonarr:/config
      - ${DATA_ROOT}:/data
    ports:
      - "8989:8989"
    restart: unless-stopped

  radarr:
    image: lscr.io/linuxserver/radarr:latest
    container_name: radarr
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
    volumes:
      - ${CONFIG_ROOT}/radarr:/config
      - ${DATA_ROOT}:/data
    ports:
      - "7878:7878"
    restart: unless-stopped

  jellyfin:
    image: jellyfin/jellyfin:latest
    container_name: jellyfin
    user: ${PUID}:${PGID}
    volumes:
      - ${CONFIG_ROOT}/jellyfin:/config
      - ${DATA_ROOT}/media:/data/media:ro
    devices:
      - /dev/dri:/dev/dri      # Intel/AMD hardware transcoding (remove if unused)
    ports:
      - "8096:8096"
    restart: unless-stopped

Change VPN_SERVICE_PROVIDER to match your provider (gluetun supports dozens, including mullvad, protonvpn, private internet access and airvpn). Remove VPN_PORT_FORWARDING if your provider does not support it.

4Deploy & Monitor the Stack

cd /opt/mediaserver
docker compose up -d

# Status and logs
docker compose ps
docker compose logs -f gluetun qbittorrent
Web UIs (on your LAN):
  • Jellyfin http://server:8096
  • qBittorrent http://server:8080
  • Prowlarr http://server:9696
  • Sonarr http://server:8989
  • Radarr http://server:7878
First Logins:
  • • Jellyfin: setup wizard, add libraries at /data/media/movies and /data/media/tv
  • • *arr apps: set Authentication to Forms and create a user
  • • qBittorrent: temporary password is in docker logs qbittorrent

Download Client Configuration

1qBittorrent First-Run Setup

  1. 1. Open http://server:8080, log in with admin and the temporary password from the logs
  2. 2. Options > Web UI: set a strong username and password immediately
  3. 3. Options > Downloads: confirm the default save path is /data/torrents
  4. 4. Run the leak test in the VPN section below before adding any torrents

2Categories & Paths for Sonarr/Radarr

# Categories (right-click Categories > Add category)
movies  -> /data/torrents/movies
tv      -> /data/torrents/tv
music   -> /data/torrents/music

# Options > Downloads
Default Torrent Management Mode:   Automatic
Default Save Path:                 /data/torrents
When Category Save Path changed:   Relocate torrent

Automatic mode lets Sonarr and Radarr assign a category and have qBittorrent place the files correctly. Do not enable "move completed" style options; the *arr apps import from the category folder and hardlink into /data/media.

3Privacy & Connection Settings

Connection:

  • UPnP / NAT-PMP: Disabled (gluetun handles ports)
  • Listening port: your VPN's forwarded port, if you have one
  • Global max connections: 200-500
  • Upload limit: set to roughly 80% of your upstream

BitTorrent Privacy:

  • Encryption: Require encryption (fewer peers, less ISP visibility)
  • Anonymous mode: Enable (hides client details)
  • DHT / PeX / LSD: Disable for private trackers; public torrents need DHT
  • Remember: peers always see your VPN IP. That's how BitTorrent works

4Without Docker: Bind to the VPN Interface

Running qBittorrent directly on a desktop with a VPN app instead? Bind it to the VPN interface so it cannot fall back to your real connection.

# Find the VPN interface name
ip -brief link

# Typical names
wg0         # WireGuard (most providers in 2026)
tun0        # OpenVPN (where still offered)
proton0     # Proton VPN app on Linux
  1. 1. Options > Advanced > Network Interface: pick the VPN interface (not "Any interface")
  2. 2. Optional IP address to bind to: All addresses
  3. 3. Test: start a legal test torrent, disconnect the VPN, confirm transfers drop to zero

Indexers & Prowlarr Configuration

1Secure Prowlarr

  1. 1. Open http://server:9696
  2. 2. Settings > General > Authentication: Forms (login page), Required: Enabled
  3. 3. Create a username and a strong password

2Add Indexers

  1. 1. Indexers > Add Indexer, search for the trackers or Usenet indexers you have accounts with
  2. 2. Enter API keys or credentials, then click Test
  3. 3. Use Tags to limit specific indexers to specific apps if needed
  4. 4. Prefer indexers that publish legal content you are entitled to download

3Sync Indexers to Sonarr & Radarr

  1. 1. In Sonarr: Settings > General, copy the API Key (do the same in Radarr)
  2. 2. In Prowlarr: Settings > Apps > + > Sonarr
  3. 3. Prowlarr Server: http://prowlarr:9696, Sonarr Server: http://sonarr:8989, paste the API key
  4. 4. Sync Level: Full Sync, Test, Save. Repeat for Radarr with http://radarr:7878
  5. 5. Sonarr/Radarr > Settings > Download Clients > qBittorrent: host gluetun, port 8080, category tv / movies

Containers on the same compose network reach each other by service name. qBittorrent is reached through gluetun because it shares that container's network.

4Optional: FlareSolverr

Some sites sit behind Cloudflare challenges. FlareSolverr runs a headless browser to solve them. Only add it if an indexer actually needs it.

  flaresolverr:
    image: ghcr.io/flaresolverr/flaresolverr:latest
    container_name: flaresolverr
    ports:
      - "8191:8191"
    restart: unless-stopped

Then in Prowlarr: Settings > Indexers > + > FlareSolverr, host http://flaresolverr:8191, add a tag, and apply that tag only to indexers that need it.

Request Management with Seerr

Overseerr and Jellyseerr merged into a single project, Seerr, in February 2026. It supports Jellyfin, Emby and Plex, and on first start it migrates existing Overseerr/Jellyseerr configs automatically. New installs should use Seerr.

1Install Seerr

  1. 1. Follow the Docker Compose instructions at docs.seerr.dev for the current image name and port
  2. 2. Add the service to your compose.yaml with a config volume under ${CONFIG_ROOT}/seerr
  3. 3. docker compose up -d and open the web UI

2Connect Jellyfin, Sonarr & Radarr

  1. 1. Sign in with your Jellyfin admin account (choose Jellyfin as the media server)
  2. 2. Jellyfin URL: http://jellyfin:8096, then select which libraries to sync
  3. 3. Settings > Services: add Radarr (http://radarr:7878) and Sonarr (http://sonarr:8989) with their API keys
  4. 4. Pick default quality profiles and root folders (/data/media/movies, /data/media/tv)

3Users & Permissions

  • • Import Jellyfin users so family members log in with their existing accounts
  • • Grant "Request" but not "Auto-Approve" to most users
  • • Set request limits per user (for example 10 movies per week)

4Approvals & Notifications

  • • Requests land in a queue; approving one sends it straight to Sonarr or Radarr
  • • Notifications: email, ntfy, Gotify, Discord, Telegram and webhooks are built in
  • • For privacy, prefer a self-hosted push service like ntfy or Gotify

VPN Integration & Privacy

1Why Gluetun

  • • One container holds the VPN tunnel plus a built-in firewall that blocks everything outside it
  • • Any container attached with network_mode: "service:gluetun" inherits the kill switch
  • • DNS goes through the tunnel by default, so your ISP never sees lookups
  • • Supports WireGuard and OpenVPN across many providers (current release: v3.41.3)

2Choosing a Provider (October 2026)

Port forwarding available:
  • • Proton VPN (paid plans, P2P servers)
  • • Private Internet Access
No port forwarding:
  • • Mullvad (removed 2023; WireGuard-only since Jan 2026)
  • • IVPN (removed 2023)

Without a forwarded port you can still download, but you connect to fewer peers and seed poorly. Avoid free VPNs for torrenting entirely.

3Port Forwarding Setup

# Proton VPN / PIA: gluetun requests a forwarded port automatically.
# Read the current port:
docker exec gluetun cat /tmp/gluetun/forwarded_port

# Then set qBittorrent > Options > Connection > Listening Port to that number.
# The port can change on reconnect; the gluetun wiki documents an
# up-command hook that pushes it into qBittorrent's API automatically.

# Providers that assign you a fixed forwarded port instead:
#   - FIREWALL_VPN_INPUT_PORTS=<your port>   (opens it on the tunnel)

4Leak Testing & Kill Switch Verification

# Gluetun's built-in health check (should print "healthy")
docker inspect --format '{{.State.Health.Status}}' gluetun

# Public IP seen through the tunnel (must be the VPN's IP, not yours)
docker exec gluetun wget -qO- https://ipinfo.io/ip

# qBittorrent shares gluetun's network stack, so this matches the line above.
# Kill switch test: stop the VPN and confirm qBittorrent loses all connectivity
docker stop gluetun
docker exec qbittorrent curl -s --max-time 5 https://ipinfo.io/ip || echo "blocked: kill switch works"
docker start gluetun && docker restart qbittorrent

If the IP check ever shows your home IP, stop qBittorrent immediately and check that it has network_mode: "service:gluetun" and no ports: section of its own.

Remote Access & Security

1Best Option: Private VPN Mesh (No Open Ports)

Reach Jellyfin from anywhere without exposing anything to the internet. Tailscale is the easiest; Headscale is a self-hosted control server for Tailscale clients; plain WireGuard (for example wg-easy) works if you can forward one UDP port on your router.

# Install Tailscale on the server
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up

# Install the Tailscale app on your phone/laptop, sign in to the same tailnet,
# then open Jellyfin at http://<server-tailscale-name>:8096
tailscale status

2If You Must Expose Jellyfin: Reverse Proxy + HTTPS

# /etc/caddy/Caddyfile  (Caddy fetches and renews HTTPS certs automatically)
jellyfin.example.com {
    reverse_proxy localhost:8096
}
  • • Caddy gets Let's Encrypt certificates automatically; Traefik and Nginx Proxy Manager are alternatives
  • • Forward only 443 on your router to the proxy
  • • Expose Jellyfin only. Never expose qBittorrent, Prowlarr, Sonarr or Radarr
  • • Your home IP becomes visible to anyone who looks up the domain

3Brute-Force Protection

  • • CrowdSec or fail2ban watching the reverse proxy and Jellyfin logs
  • • Jellyfin: Dashboard > Users, disable the admin account's remote access, set login attempt limits
  • • Unique, long passwords for every user

4Domain & DNS

  • • Use a subdomain you control; enable WHOIS privacy at the registrar
  • • Dynamic home IP? Run a DDNS updater container for your DNS provider
  • • For the server's own lookups, Quad9 (9.9.9.9) validates DNSSEC on all endpoints since June 2026. Mullvad's public DNS shuts down November 2, 2026

Mobile Apps & Clients

1Official Jellyfin Clients

  • • Android: Jellyfin (Play Store, F-Droid)
  • • iOS / iPadOS: Jellyfin Mobile, or Swiftfin (native app from the Jellyfin project)
  • • Android TV / Fire TV: Jellyfin for Android TV
  • • Roku, LG webOS, Samsung Tizen: official apps (check jellyfin.org/downloads)
  • • Desktop: any browser, or the official desktop client
  • • Kodi: Jellyfin add-on

2Popular Third-Party Clients

  • • Findroid (Android): open source, native UI, offline downloads
  • • Infuse (Apple devices): polished player, paid subscription for some features

3Connecting Clients

  1. 1. At home: http://server-ip:8096
  2. 2. Away: Tailscale address (Step 1 above) or your HTTPS domain
  3. 3. Use Quick Connect (Dashboard > General) to sign in TVs without typing passwords

4Offline Downloads & Casting

  • • Enable "Allow media downloading" per user in Dashboard > Users
  • • Jellyfin's Android app supports Chromecast; Apple clients support AirPlay
  • • Direct Play (no transcoding) saves CPU: pick clients that support your file formats

Monitoring & Maintenance

1Uptime Monitoring

Uptime Kuma checks every service and alerts you (ntfy, email, Telegram and more) when something goes down. Add it to compose.yaml:

  uptime-kuma:
    image: louislam/uptime-kuma:latest
    container_name: uptime-kuma
    volumes:
      - ${CONFIG_ROOT}/uptime-kuma:/app/data
    ports:
      - "3001:3001"
    restart: unless-stopped

2Updating Safely

cd /opt/mediaserver

# Pull new images and recreate only containers that changed
docker compose pull
docker compose up -d

# Clean up old image layers
docker image prune -f

The original Watchtower auto-updater was archived in December 2025. Update manually on a schedule (read release notes first, especially for Jellyfin major versions), or use a notify-only tool such as Diun so you know when images change.

3Backups

# Nightly config backup (media is too big; back it up separately or re-acquire)
cd /opt/mediaserver
docker compose stop
tar -czf ~/backups/mediaserver-config-$(date +%F).tar.gz config compose.yaml .env
docker compose start

# Keep the 14 most recent archives
ls -1t ~/backups/mediaserver-config-*.tar.gz | tail -n +15 | xargs -r rm

Sonarr, Radarr and Prowlarr also make their own scheduled backups (System > Backup). Copy archives off the server, ideally encrypted (restic or Borg).

4Troubleshooting Checklist

  • • Imports fail: paths differ between containers. Every app must see the same /data path
  • • Files copied, not hardlinked: torrents and media are on different filesystems or mounts
  • • qBittorrent unreachable: gluetun is unhealthy. Check docker compose logs gluetun
  • • Buffering: check Jellyfin Dashboard > Activity for transcode reasons and enable hardware acceleration

Self-Hosted vs Commercial Streaming

Self-Hosted Advantages

  • No monthly subscription fees
  • Complete content control and ownership
  • No tracking or data collection
  • Quality limited only by your storage

Considerations

  • Initial setup complexity
  • Hardware and electricity costs
  • Maintenance and updates required
  • You are responsible for your own security

Resources & Project Links