Featured image of post Jellyfin VPS Deployment Guide: Build Your Personal Netflix with a Self-Hosted Media Server

Jellyfin VPS Deployment Guide: Build Your Personal Netflix with a Self-Hosted Media Server

Complete tutorial for deploying Jellyfin media server on VPS using Docker — with hardware transcoding, multi-device streaming, remote access, and automated management for your private home cinema

Why Jellyfin?

Do you have hundreds of movies and TV shows sitting unused because you don’t have a proper way to organize and watch them? NAS devices are expensive, and streaming subscriptions mean ongoing monthly costs.

Jellyfin is a completely free, open-source media server that transforms your VPS into a personal media hub. It supports automatic media library management, multi-device streaming, hardware transcoding, and remote access — the best free alternative to Plex and Emby.

Jellyfin Feature Comparison

FeatureJellyfinPlexEmby
PriceCompletely FreePremium features paidSome features paid
Open Source✅ Fully Open Source❌ Closed Source⚠️ Partially Open
Hardware TranscodingFreePaid (Plex Pass)Paid
Self-Hosted
Platform SupportAll PlatformsAll PlatformsAll Platforms
  • 🎬 Automated Media Management — Auto-download posters, synopsis, and metadata
  • 📱 Multi-Device Support — Access from phones, tablets, TVs, and browsers
  • 🔄 Real-Time Transcoding — Auto-adjust quality based on network bandwidth
  • 🔒 Fully Private — Complete control over your data

Environment Preparation

Use CaseMinimumRecommended
Light Use (1080p Software Decode)1 Core 2GB2 Core 4GB
Hardware Transcoding (Recommended)1 Core 2GB + GPU2 Core 4GB + GPU
Large Media Library2 Core 4GB4 Core 8GB

Required Software

  • Docker ≥ 20.10
  • Docker Compose ≥ 2.0
  • Linux VPS (Ubuntu 22.04/24.04 or Debian 12 recommended)

Step 1: Install Docker

If Docker isn’t installed on your VPS:

# Install Docker
curl -fsSL https://get.docker.com | sh

# Add current user to docker group (avoids sudo every time)
sudo usermod -aG docker $USER

# Enable and start Docker
sudo systemctl enable docker
sudo systemctl start docker

# Verify installation
docker --version
docker compose version

⚠️ Log out and back in (or run newgrp docker) for group changes to take effect.


Step 2: Create Directory Structure

# Create project directory
mkdir -p ~/jellyfin/{config,media,music,transcode}

# Create media directories
mkdir -p ~/jellyfin/media/{movies,tv-shows,anime}
mkdir -p ~/jellyfin/music

# Set permissions (needed for GPU transcoding)
sudo chmod -R 755 ~/jellyfin

Directory Purpose

  • config/ — Jellyfin configuration, database, and cache
  • media/ — Movies, TV shows, and other media files
  • music/ — Music files
  • transcode/ — Transcoded temporary file output

Step 3: Docker Compose Configuration

Create ~/jellyfin/docker-compose.yml:

services:
  jellyfin:
    image: jellyfin/jellyfin:latest
    container_name: jellyfin
    restart: unless-stopped
    user: 1000:1000
    ports:
      - "8096:8096"      # HTTP port
      - "8920:8920"      # HTTPS port
      - "7359:7359/udp"  # Service discovery (UPnP)
      - "1900:1900/udp"  # DLNA
    volumes:
      - ./config:/config
      - ./media:/media
      - ./music:/music
      - ./transcode:/transcode
    environment:
      - TZ=Asia/Shanghai
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.jellyfin.rule=Host(`jellyfin.yourdomain.com`)"
      - "traefik.http.services.jellyfin.loadbalancer.server.port=8096"

For hardware transcoding with an NVIDIA GPU, add:

    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: 1
              capabilities: [gpu]
    environment:
      - TZ=Asia/Shanghai
      - JELLYFIN_PublishedServerUrl=jellyfin.yourdomain.com

⚠️ Note: NVIDIA GPU transcoding requires installing the NVIDIA Container Toolkit.


Step 4: Start Jellyfin

cd ~/jellyfin
docker compose up -d

Visit http://your-vps-ip:8096 to see the Jellyfin initial setup wizard.


Step 5: Initial Setup

1. Create Admin Account

Follow the wizard to set up your admin username and password.

2. Add Media Libraries

Add your media folders in the settings panel:

  • Movies — Point to /media/movies
  • TV Shows — Point to /media/tv-shows
  • Anime — Point to /media/anime (optional)
  • Music — Point to /music

3. Configure Metadata Scrapers

Jellyfin automatically downloads posters, synopses, and other info from:

  • TheMovieDB (default)
  • TheTVDB
  • TheAudioDB (music)

Enable metadata in your preferred language for localized titles and descriptions.


Software transcoding puts heavy CPU load, especially for 1080p/4K content. Hardware transcoding significantly reduces resource usage.

Intel Quick Sync Video (QSV)

For VPS or servers with Intel integrated GPU:

    devices:
      - /dev/dri:/dev/dri
    environment:
      - TZ=Asia/Shanghai
      - NVIDIA_DRIVER_CAPABILITIES=all

NVIDIA GPU

    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: 1
              capabilities: [gpu]
    environment:
      - TZ=Asia/Shanghai

Verify Transcoding Works

In the Jellyfin dashboard → Dashboard → Transcoding, check that hardware transcoding status shows as “available.”


Step 7: Reverse Proxy and HTTPS

A reverse proxy with HTTPS is strongly recommended for production use.

Using Caddy

jellyfin.yourdomain.com {
    reverse_proxy localhost:8096
    
    encode gzip zstd
    
    header {
        Strict-Transport-Security "max-age=31536000; includeSubDomains"
        X-Content-Type-Options "nosniff"
    }
}

Using Nginx

server {
    listen 443 ssl http2;
    server_name jellyfin.yourdomain.com;

    ssl_certificate     /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:8096;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        
        # WebSocket support for remote streaming
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

Step 8: Organize Media Files

Proper file organization is critical for Jellyfin’s metadata matching.

~/jellyfin/media/
├── movies/
│   ├── The Shawshank Redemption (1994)/
│   │   ├── The.Shawshank.Redemption.1994.1080p.mkv
│   │   ├── poster.jpg          # Optional, Jellyfin auto-downloads
│   │   └── fanart.jpg          # Optional
│   ├── Inception (2010)/
│   │   └── Inception.2010.4K.mkv
├── tv-shows/
│   ├── Breaking Bad/
│   │   ├── Season 1/
│   │   │   ├── Breaking Bad - S01E01 - Pilot.mkv
│   │   │   └── Breaking Bad - S01E02 - Cat's in the Bag.mkv
│   │   └── Season 2/
├── anime/
│   ├── Attack on Titan/
│   │   ├── Season 1/
│   │   └── Season 2/
└── music/
    ├── Artist Name/
    │   └── Album Name/
    │       ├── 01 - Song Title.flac
    │       └── 02 - Another Song.flac

File Naming Conventions

Jellyfin uses file paths and names to match metadata. Follow these rules for best results:

  • Movies: Movie Title (Year).extension
  • TV Shows: Show Name/Season XX/Show Name - S0XE0Y - Episode Title.extension
  • Music: Artist Name/Album Name/TrackNumber - Song Title.extension

Step 9: Remote Access and External Streaming

Jellyfin supports multiple remote access solutions:

Zero configuration, no port exposure, most secure:

# Install cloudflared
curl -fsSL https://bin.equinox.io/c/bNyj1mQVY4c/cloudflared-stable-linux-amd64.tgz | tar xz
sudo mv cloudflared /usr/local/bin/

# Authenticate
cloudflared tunnel login

# Create tunnel
cloudflared tunnel create jellyfin
cloudflared tunnel route dns jellyfin jellyfin.yourdomain.com

# Configure
cloudflared tunnel config jellyfin

Option 2: Tailscale

If all your client devices have Tailscale installed:

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

# Access Jellyfin directly via Tailscale IP + port

Option 3: frp Reverse Tunnel

For low-latency streaming scenarios:

# frps.toml (server)
bindPort = 7000

# frpc.toml (client/VPS)
serverAddr = "your-server.com"
serverPort = 7000

[[proxies]]
name = "jellyfin"
type = "tcp"
localIP = "127.0.0.1"
localPort = 8096
remotePort = 8096

Step 10: Backup and Automation

Backup Jellyfin Configuration

#!/bin/bash
# backup-jellyfin.sh

BACKUP_DIR="/root/backups/jellyfin"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
mkdir -p "$BACKUP_DIR"

# Backup configuration
tar czf "$BACKUP_DIR/config_$TIMESTAMP.tar.gz" -C ~/jellyfin/config .

# Keep only last 30 days of backups
find "$BACKUP_DIR" -name "config_*.tar.gz" -mtime +30 -delete

echo "Backup completed: $BACKUP_DIR/config_$TIMESTAMP.tar.gz"

Add to crontab for daily automatic backup:

crontab -e
# Add this line (daily backup at 3 AM)
0 3 * * * /root/backup-jellyfin.sh

Docker Auto-Restart

The restart: unless-stopped policy in docker-compose.yml ensures Jellyfin automatically starts after VPS reboots.


Troubleshooting

Q1: Videos Won’t Play or Buffer Slowly

  • Verify transcoding configuration is correct
  • For remote access, ensure sufficient bandwidth (1080p: 5Mbps+, 4K: 25Mbps+)
  • Enable hardware transcoding for significant performance improvement
  • Check WebSocket support in proxy configuration

Q2: Poor Metadata Matching

  • Ensure proper file naming conventions (Movie Title Year.extension)
  • Manually edit media info in Jellyfin
  • Use FileBot for bulk file renaming
  • Try different metadata sources (TheMovieDB / TheTVDB)

Q3: Insufficient Disk Space

  • Mount external storage (NAS, object storage)
  • Configure Jellyfin cache path to a different partition
  • Regularly clean transcode temp files (transcode/ directory)
  • Consider compressed video formats (HEVC/H.265)

Q4: Mobile App Can’t Connect

  • Verify firewall rules allow the required ports
  • If using a reverse proxy, confirm DNS resolution
  • Try direct IP + port access (on local network)
  • Install the official Jellyfin app (Android / iOS)

Summary

This guide covered building a Jellyfin media server on VPS from scratch. Key takeaways:

  1. Docker One-Click Deployment — Simple and reliable with docker compose
  2. Hardware Transcoding — Significantly reduces CPU load, enables 4K streaming
  3. Reverse Proxy + HTTPS — Foundation for security and remote access
  4. Proper Media Organization — Well-structured file layouts enable accurate metadata matching
  5. Secure Remote Access — Cloudflare Tunnel or Tailscale for safe external access

Your VPS is now a powerful home cinema. Whether 1080p or 4K, at home or on the go, enjoy your personal media library anytime, anywhere.


Appendix: Complete docker-compose.yml

services:
  jellyfin:
    image: jellyfin/jellyfin:latest
    container_name: jellyfin
    restart: unless-stopped
    user: 1000:1000
    ports:
      - "8096:8096"
      - "8920:8920"
      - "7359:7359/udp"
      - "1900:1900/udp"
    volumes:
      - ./config:/config
      - ./media:/media
      - ./music:/music
      - ./transcode:/transcode
    environment:
      - TZ=Asia/Shanghai
    # Uncomment for GPU transcoding
    # deploy:
    #   resources:
    #     reservations:
    #       devices:
    #         - driver: nvidia
    #           count: 1
    #           capabilities: [gpu]

📌 Next Steps: Set up automated media downloading (Jackett + Sonarr + Radarr) to keep your library automatically updated.

📺 看视频版教程 → DuckDB Lab YouTube

Subscribe for more DuckDB & AI automation tutorials