Running a media server at home means exposing your network to the internet, dealing with dynamic IP addresses, and competing with your household's bandwidth every time you want to stream remotely. Plex has become the default choice for most people, but it increasingly pushes users toward paid subscriptions, injects its own content into your library, and routes traffic through its servers even when it shouldn't need to. Jellyfin is a completely free, open-source alternative with no paid tiers, no telemetry, no accounts, and no central server dependency.

Running Jellyfin on a VPS solves the home network problem entirely. Your media lives in a datacenter with dedicated bandwidth, accessible from anywhere in the world without port forwarding, dynamic DNS, or NAT traversal. You get a streaming service that's entirely yours — no subscriptions, no content injection, no tracking, and no arbitrary limitations on features.

MassiveGRID Ubuntu VPS includes: Ubuntu 24.04 LTS pre-installed · Proxmox HA cluster with automatic failover · Ceph 3x replicated NVMe storage · Independent CPU/RAM/storage scaling · 12 Tbps DDoS protection · 4 global datacenter locations · 100% uptime SLA · 24/7 human support rated 9.5/10

Deploy a self-managed VPS — from $1.99/mo
Need dedicated resources? — from $19.80/mo
Want fully managed hosting? — we handle everything

Sizing Your VPS for Jellyfin

Jellyfin's resource requirements depend primarily on two factors: the size of your media library and whether you need real-time transcoding. If your clients can direct-play your media files (no format conversion needed), the CPU requirements are minimal. Transcoding — converting video on the fly to a format or resolution the client can handle — is extremely CPU-intensive.

Use Case Recommended VPS Storage
Small library (up to 500GB), 1-2 streams, direct play 2 vCPU / 2 GB RAM 500 GB+
Medium library (1-2TB), 2-3 streams, occasional transcoding 4 vCPU / 4 GB RAM 2 TB+
Large library (5TB+), 3+ concurrent streams, frequent transcoding Dedicated VPS 8+ vCPU / 16 GB RAM 5 TB+

Sizing tip: For a small media library (up to 500GB) with 1-2 concurrent streams, deploy a Cloud VPS with 4 vCPU / 4GB RAM. Start with direct play and only invest in transcoding capacity if your clients need it.

Prerequisites

Before starting, ensure you have:

Check your available disk space:

df -h /

If you need more storage, MassiveGRID allows independent scaling of CPU, RAM, and storage — you can add storage without changing your compute plan.

Installing Jellyfin with Docker

Create the directory structure for Jellyfin:

sudo mkdir -p /opt/jellyfin
sudo mkdir -p /media/library/movies
sudo mkdir -p /media/library/shows
sudo mkdir -p /media/library/music
cd /opt/jellyfin

Create the Docker Compose file:

sudo nano /opt/jellyfin/docker-compose.yml
services:
  jellyfin:
    image: jellyfin/jellyfin:latest
    container_name: jellyfin
    restart: unless-stopped
    ports:
      - "127.0.0.1:8096:8096"
    volumes:
      - jellyfin-config:/config
      - jellyfin-cache:/cache
      - /media/library:/media:ro
    environment:
      - JELLYFIN_PublishedServerUrl=https://media.yourdomain.com
    # Uncomment for hardware transcoding (if available):
    # devices:
    #   - /dev/dri:/dev/dri
    # group_add:
    #   - "109"  # render group ID (check with: getent group render)

volumes:
  jellyfin-config:
  jellyfin-cache:

Key configuration notes:

Start Jellyfin:

cd /opt/jellyfin
sudo docker compose up -d

Verify it's running:

sudo docker compose ps
curl -s http://127.0.0.1:8096/health

Initial Configuration and Library Setup

Before setting up Nginx, let's complete the initial configuration. Temporarily open port 8096 for setup, or use an SSH tunnel:

# SSH tunnel from your local machine:
ssh -L 8096:127.0.0.1:8096 user@your-vps-ip

Then open http://localhost:8096 in your browser. The setup wizard walks you through:

  1. Language: Choose your preferred display language
  2. User account: Create the admin account with a strong password
  3. Media libraries: Add your media directories
  4. Metadata: Configure metadata providers (TMDb for movies/TV, MusicBrainz for music)
  5. Remote access: Leave the defaults for now since we'll use Nginx

When adding media libraries, configure each type:

Movies Library

TV Shows Library

Music Library

Jellyfin expects your files to be named following a specific convention for automatic metadata matching:

# Movies
/media/library/movies/Movie Name (2024)/Movie Name (2024).mkv

# TV Shows
/media/library/shows/Show Name (2024)/Season 01/Show Name S01E01.mkv
/media/library/shows/Show Name (2024)/Season 01/Show Name S01E02.mkv

# Music
/media/library/music/Artist Name/Album Name (2024)/01 - Track Name.flac

Uploading Media to Your VPS

Getting your media files onto the VPS is often the most time-consuming part of the setup. Here are the most practical methods.

rsync (Best for Large Transfers)

rsync is the best tool for transferring large media libraries because it can resume interrupted transfers and only sends differences on subsequent syncs:

# Initial upload — from your local machine
rsync -avhP --partial /path/to/local/movies/ user@your-vps-ip:/media/library/movies/

# Subsequent syncs — only transfers new/changed files
rsync -avhP --partial /path/to/local/movies/ user@your-vps-ip:/media/library/movies/

The flags explained:

For very large uploads over unreliable connections, use screen or tmux on the VPS to keep the transfer running even if your SSH session drops:

# On the VPS
sudo apt install screen
screen -S upload

# Start the rsync from within the screen session
rsync -avhP --partial user@source-machine:/path/to/media/ /media/library/

# Detach from screen: Ctrl+A then D
# Reattach later: screen -r upload

SFTP (For Selective Uploads)

For uploading individual files or small batches, SFTP with a graphical client like FileZilla or WinSCP is more convenient:

# Command-line SFTP
sftp user@your-vps-ip
sftp> cd /media/library/movies
sftp> put -r "Movie Name (2024)"
sftp> exit

Rclone (For Cloud Storage Sources)

If your media is in cloud storage (Google Drive, Dropbox, etc.), rclone can transfer it directly to your VPS without downloading to your local machine first:

# Install rclone on the VPS
sudo apt install rclone

# Configure a remote (interactive)
rclone config

# Copy from cloud to VPS
rclone copy gdrive:Media/Movies /media/library/movies --progress

After uploading, trigger a library scan in Jellyfin: go to Dashboard → Libraries and click Scan All Libraries, or use the API:

curl -X POST "http://127.0.0.1:8096/Library/Refresh" \
  -H "X-Emby-Token: your-api-key"

Nginx Reverse Proxy with SSL

Set up Nginx to serve Jellyfin over HTTPS. This provides SSL encryption, proper domain access, and the ability to add security headers.

Create the Nginx configuration:

sudo nano /etc/nginx/sites-available/media.yourdomain.com
server {
    listen 80;
    server_name media.yourdomain.com;

    # Increase max upload size for media uploads via web interface
    client_max_body_size 20G;

    # Security headers
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Referrer-Policy "no-referrer-when-downgrade" always;

    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;
        proxy_set_header X-Forwarded-Protocol $scheme;
        proxy_set_header X-Forwarded-Host $host;

        # Disable buffering for streaming
        proxy_buffering off;
    }

    # WebSocket support for SyncPlay and real-time updates
    location /socket {
        proxy_pass http://127.0.0.1:8096;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        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;
    }
}

Enable and test:

sudo ln -s /etc/nginx/sites-available/media.yourdomain.com /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Get your SSL certificate:

sudo certbot --nginx -d media.yourdomain.com

For the full SSL setup walkthrough, see our Let's Encrypt SSL guide. For more on reverse proxy configuration, see our Nginx reverse proxy guide.

After Certbot completes, visit https://media.yourdomain.com in your browser. You should see the Jellyfin login page.

Update the Jellyfin server URL in Dashboard → Networking: set the "Base URL" field if you're serving from a subpath, or leave it empty if Jellyfin is at the domain root.

Transcoding Configuration and Performance

Transcoding converts media from one format/resolution to another in real time. This happens when a client can't play the original format directly — for example, playing an MKV file with DTS audio on a device that only supports AAC.

Navigate to Dashboard → Playback → Transcoding to configure:

Software Transcoding (CPU-Based)

Software transcoding uses your CPU and is available on every VPS. Configure it in Jellyfin's dashboard:

A rough guide to CPU requirements for software transcoding:

Resolution Codec vCPUs Needed (Per Stream)
1080p → 720p H.264 2-4 vCPU
4K → 1080p H.264 6-8 vCPU
4K → 1080p H.265/HEVC 8-12 vCPU
Any (audio only) AAC/MP3 0.5 vCPU

Need dedicated CPU for transcoding? Video transcoding is the most CPU-intensive workload you'll run on a VPS. Dedicated CPU ensures smooth playback without contention from other tenants on shared infrastructure. If you're transcoding 4K content, dedicated resources are essential.

Minimizing Transcoding

The best transcoding strategy is to avoid transcoding. Encode your media in formats that most clients can direct-play:

You can pre-transcode your library offline using FFmpeg to avoid real-time transcoding entirely:

# Convert a file to a more compatible format
ffmpeg -i input.mkv \
  -c:v libx264 -crf 20 -preset slow \
  -c:a aac -b:a 192k \
  -c:s copy \
  output.mkv

Install FFmpeg on your VPS for pre-processing:

sudo apt install ffmpeg

Hardware Acceleration

If your VPS provides GPU or hardware acceleration capabilities, you can dramatically reduce CPU usage for transcoding. Jellyfin supports several hardware acceleration methods:

To use VA-API with an Intel GPU, first verify the device is available:

ls -la /dev/dri/
# Should show renderD128

Then uncomment the device passthrough in your Docker Compose file:

    devices:
      - /dev/dri:/dev/dri
    group_add:
      - "109"  # render group ID — verify with: getent group render

In Jellyfin's transcoding settings, set Hardware acceleration to Video Acceleration API (VA-API) and configure the VA-API device as /dev/dri/renderD128.

Restart the container after making changes:

cd /opt/jellyfin
sudo docker compose down
sudo docker compose up -d

Note that hardware acceleration availability depends entirely on your hosting provider and VPS type. Standard cloud VPS instances typically don't have GPU access — this is more relevant for dedicated servers or GPU-enabled instances.

User Accounts and Access Control

Create user accounts for family members or friends who'll use your Jellyfin server.

Navigate to Dashboard → Users → Add User:

For bandwidth management across multiple users, set per-user remote streaming bitrate limits:

# Recommended remote streaming bitrates:
# 720p: 4 Mbps
# 1080p: 8 Mbps
# 1080p (high quality): 15 Mbps
# 4K: 40 Mbps (not recommended for remote streaming)

Configure these in each user's settings under Playback → Internet streaming quality.

Bandwidth Considerations and Quality Settings

Streaming media requires consistent bandwidth. Calculate your needs:

Quality Bitrate Bandwidth per Stream Monthly Data (8 hrs/day)
720p 4 Mbps ~0.5 MB/s ~432 GB
1080p 8 Mbps ~1 MB/s ~864 GB
1080p High 15 Mbps ~1.9 MB/s ~1.6 TB
4K 40 Mbps ~5 MB/s ~4.3 TB

Set a global remote streaming limit in Dashboard → Playback to prevent any single user from consuming all your bandwidth. A limit of 8-15 Mbps per stream is a good balance between quality and bandwidth conservation.

To monitor bandwidth usage, check your Nginx access logs or use a monitoring tool. See our monitoring setup guide for comprehensive server monitoring.

Mobile and TV App Setup

Jellyfin has official and third-party clients for every major platform:

Mobile

TV and Streaming Devices

Desktop

In each client, set the server address to https://media.yourdomain.com and log in with your Jellyfin credentials. The apps will discover all your libraries and begin syncing metadata.

Storage Management and Expansion

Media libraries grow over time. Here's how to manage storage effectively on your VPS.

Monitor Disk Usage

# Overall disk usage
df -h /media

# Breakdown by library
du -sh /media/library/movies
du -sh /media/library/shows
du -sh /media/library/music

# Find the largest files
du -ah /media/library | sort -rh | head -20

Set Up Storage Alerts

Create a simple monitoring script that alerts you when storage is getting low:

sudo nano /opt/jellyfin/check-storage.sh
#!/bin/bash

THRESHOLD=90  # Alert when disk usage exceeds 90%
USAGE=$(df /media --output=pcent | tail -1 | tr -d ' %')

if [ "$USAGE" -ge "$THRESHOLD" ]; then
    echo "WARNING: Media storage at ${USAGE}% capacity on $(hostname)" | \
    mail -s "Jellyfin Storage Alert" admin@yourdomain.com
fi
sudo chmod +x /opt/jellyfin/check-storage.sh

# Run every 6 hours
echo "0 */6 * * * /opt/jellyfin/check-storage.sh" | sudo tee -a /var/spool/cron/crontabs/root

For more on cron scheduling, see our cron jobs guide.

Optimizing Storage

Reduce storage usage without losing quality:

# Check the codec and bitrate of a file
ffprobe -v quiet -show_entries format=duration,size,bit_rate -show_entries stream=codec_name,width,height,bit_rate input.mkv

# Re-encode a file with lower bitrate (CRF 23 is visually transparent for most content)
ffmpeg -i input.mkv -c:v libx264 -crf 23 -preset medium -c:a copy output.mkv

# Batch re-encode all MKV files in a directory
find /media/library/movies -name "*.mkv" -exec ffprobe -v quiet -show_entries format=bit_rate {} \;

A script to identify files that would benefit from re-encoding (files with excessively high bitrates):

sudo nano /opt/jellyfin/find-large-files.sh
#!/bin/bash
echo "Files with bitrate over 15 Mbps:"
find /media/library -type f \( -name "*.mkv" -o -name "*.mp4" -o -name "*.avi" \) | while read -r file; do
    bitrate=$(ffprobe -v quiet -show_entries format=bit_rate -of csv=p=0 "$file" 2>/dev/null)
    if [ -n "$bitrate" ] && [ "$bitrate" -gt 15000000 ]; then
        size=$(du -sh "$file" | cut -f1)
        echo "  $size  $(( bitrate / 1000000 )) Mbps  $file"
    fi
done | sort -rh

Backing Up Jellyfin Configuration

Your media files are large and presumably backed up elsewhere — but your Jellyfin configuration (user accounts, library settings, watch history, metadata) should be backed up regularly.

sudo nano /opt/jellyfin/backup-config.sh
#!/bin/bash

BACKUP_DIR="/opt/jellyfin/backups"
RETENTION_DAYS=30
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
CONFIG_VOLUME=$(docker volume inspect jellyfin-config --format '{{ .Mountpoint }}')

mkdir -p "$BACKUP_DIR"

# Backup the config volume (excludes cache and media)
tar -czf "$BACKUP_DIR/jellyfin-config_$TIMESTAMP.tar.gz" \
    -C "$CONFIG_VOLUME" .

# Remove old backups
find "$BACKUP_DIR" -name "jellyfin-config_*.tar.gz" -mtime +$RETENTION_DAYS -delete

echo "Config backup completed: jellyfin-config_$TIMESTAMP.tar.gz ($(du -sh "$BACKUP_DIR/jellyfin-config_$TIMESTAMP.tar.gz" | cut -f1))"
sudo chmod +x /opt/jellyfin/backup-config.sh

# Add to crontab — run weekly
sudo crontab -e

Add:

0 4 * * 0 /opt/jellyfin/backup-config.sh >> /var/log/jellyfin-backup.log 2>&1

For a comprehensive backup strategy, see our automatic backups guide.

Updating Jellyfin

Keep Jellyfin up to date for bug fixes and new features:

# Backup config before updating
sudo /opt/jellyfin/backup-config.sh

# Pull the latest image
cd /opt/jellyfin
sudo docker compose pull

# Restart with the new version
sudo docker compose down
sudo docker compose up -d

# Check the version
sudo docker compose logs jellyfin | head -10

Troubleshooting Common Issues

Check logs for detailed error information:

# Jellyfin container logs
sudo docker compose logs -f jellyfin

# Resource usage
docker stats jellyfin --no-stream

For system-wide monitoring, see our VPS monitoring guide. For log management best practices, see our log management guide.

Want managed media infrastructure? If you'd rather not manage server updates, storage scaling, and performance tuning yourself, MassiveGRID's managed dedicated cloud servers give you the performance of bare metal with the convenience of full management — we handle the infrastructure so you can focus on your media library.

Final Thoughts

Running Jellyfin on a VPS gives you a personal streaming service with none of the compromises of commercial platforms. There's no subscription fee, no content injection, no telemetry, and no limits on users or features. Your media is served from datacenter-grade infrastructure with consistent bandwidth, accessible from any device, anywhere in the world.

Start with a MassiveGRID Cloud VPS sized for your library and streaming needs. Focus on direct play to minimize CPU requirements, and scale up to a dedicated VPS only if you need frequent transcoding. With the Nginx reverse proxy, SSL, and backup configuration covered in this guide, you have a production-ready media server that's ready for daily use.

For related infrastructure, check out our guide on securing your Ubuntu VPS and optimizing VPS performance to ensure your media server runs smoothly and securely.