Comprehensive Guide to Hosting and Securing Jellyfin on Ubuntu 20.04+ with Docker

Written by:

Introduction:

Jellyfin is a free and open-source media server that allows you to manage and stream your media collection to any device. Unlike proprietary alternatives like Plex or Emby, Jellyfin is completely free, has no premium features locked behind paywalls, and doesn’t collect user data. This guide will walk you through every step of setting up a secure, well-configured Jellyfin instance on Ubuntu 20.04 or newer using Docker.

Why Use Docker for Jellyfin?

Docker deployment offers several key advantages:

  • Simplified installation process
  • Consistent environments across different systems
  • Easy updates without dependency conflicts
  • Enhanced security through container isolation
  • Simplified backup and restore procedures

Instruction layout/flow

Prerequisites

Before proceeding with installation, ensure your system meets these requirements:

System Requirements

  • Ubuntu 20.04 LTS or newer
  • 2GB RAM minimum (4GB recommended for transcoding)
  • Dual-core CPU minimum (quad-core recommended for transcoding)
  • Sufficient storage for your media library
  • Network connectivity (port forwarding required for remote access)

You can check your Ubuntu version with:

lsb_release -a

Software Requirements

  • Docker Engine
  • Docker Compose
  • Git (optional, for cloning configuration files)

Required Permissions

You’ll need:

  • Sudo access for installation
  • User added to docker group for non-root Docker access

Check your current user ID and group ID (needed for Docker configuration):

#command is: id
id

Installing Docker and Docker Compose

Follow these steps to install Docker and Docker Compose on your Ubuntu system:

Step 1: Update System Packages

sudo apt update && sudo apt upgrade -y

This ensures your system has the latest security updates before proceeding.

Step 2: Install Required Dependencies

sudo apt install apt-transport-https ca-certificates curl software-properties-common gnupg -y

These packages are necessary for adding Docker’s repository and securely downloading packages.

Step 3: Add Docker’s Official GPG Key

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg

This adds Docker’s official GPG key to verify package integrity.

Step 4: Add Docker Repository

echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

This adds Docker’s official repository to your system’s package sources.

Step 5: Update Package Database

sudo apt update

This refreshes your package database to include Docker packages.

Step 6: Install Docker Engine

sudo apt install docker-ce docker-ce-cli containerd.io -y

This installs Docker Engine, CLI, and containerd.

Step 7: Enable Docker Service

sudo systemctl enable --now docker

This starts Docker and configures it to launch at system boot.

Step 8: Add User to Docker Group

sudo usermod -aG docker $USER

This allows running Docker commands without sudo. Important: You’ll need to log out and back in for changes to take effect.

Step 9: Install Docker Compose

sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose

This downloads the latest Docker Compose binary and makes it executable.

Step 10: Verify Installation

docker --version
docker-compose --version

This confirms that both Docker and Docker Compose are correctly installed.

Setting Up Jellyfin with Docker Compose

Now that Docker is installed, we’ll set up Jellyfin using Docker Compose for easier management of container configurations.

Step 1: Create Directory Structure

mkdir -p ~/docker_data/jellyfin/{config,cache,media}

This creates directories for Jellyfin’s configuration, cache, and media files.

Step 2: Create Docker Compose Configuration

Create a new file at ~/docker_data/jellyfin/docker-compose.yml with the following content:

cat > ~/docker_data/jellyfin/docker-compose.yml << 'EOF'
version: '3'

services:
  jellyfin:
    image: jellyfin/jellyfin:latest
    container_name: jellyfin
    user: 1000:1000  # Replace with your user ID and group ID from 'id' command
    restart: unless-stopped
    volumes:
      - ./config:/config
      - ./cache:/cache
      - ./media:/media
      # Optional: mount additional media locations
      # - /path/to/media:/media2:ro
    ports:
      - 8096:8096  # HTTP port (remove for Traefik-only access)
    environment:
      - TZ=Etc/UTC  # Set to your timezone
    networks:
      - proxy  # For Traefik integration
    #devices:  # Uncomment for hardware acceleration
    #  - /dev/dri:/dev/dri  # Intel GPU
    #  - /dev/video10:/dev/video10  # VAAPI rendering node
    #  - /dev/video11:/dev/video11  # VAAPI DRM node
    #  - /dev/nvidia0:/dev/nvidia0  # Nvidia GPU
    #  - /dev/nvidiactl:/dev/nvidiactl
    #  - /dev/nvidia-modeset:/dev/nvidia-modeset
    #  - /dev/nvidia-uvm:/dev/nvidia-uvm
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.jellyfin.rule=Host(`jellyfin.your-domain.com`)"
      - "traefik.http.routers.jellyfin.entrypoints=websecure"
      - "traefik.http.routers.jellyfin.tls.certresolver=letsencrypt"
      - "traefik.http.services.jellyfin.loadbalancer.server.port=8096"

networks:
  proxy:
    external: true
EOF

Important: Replace 1000:1000 with your actual user ID and group ID from the id command, and jellyfin.your-domain.com with your actual domain.

Setting Up Traefik as a Reverse Proxy (Optional but Recommended)

Traefik provides secure access to Jellyfin from the internet with automatic SSL certificate management.

Step 1: Create Traefik Directory Structure

mkdir -p ~/docker_data/traefik/{config,letsencrypt}
touch ~/docker_data/traefik/acme.json
chmod 600 ~/docker_data/traefik/acme.json

This creates directories for Traefik configuration and Let’s Encrypt certificates.

Step 2: Create Traefik Configuration Files

Create the main configuration file:

cat > ~/docker_data/traefik/config/traefik.toml << 'EOF'
[entryPoints]
  [entryPoints.web]
    address = ":80"
    [entryPoints.web.http.redirections.entryPoint]
      to = "websecure"
      scheme = "https"
  
  [entryPoints.websecure]
    address = ":443"
    [entryPoints.websecure.http.tls]
      certResolver = "letsencrypt"
    [entryPoints.websecure.http.middlewares]
      middlewares = ["securityHeaders@file"]

[providers]
  [providers.docker]
    endpoint = "unix:///var/run/docker.sock"
    exposedByDefault = false
    network = "proxy"
  
  [providers.file]
    filename = "/etc/traefik/dynamic.toml"

[certificatesResolvers]
  [certificatesResolvers.letsencrypt]
    [certificatesResolvers.letsencrypt.acme]
      email = "your-email@example.com"  # Replace with your email
      storage = "/letsencrypt/acme.json"
      [certificatesResolvers.letsencrypt.acme.tlsChallenge]

[api]
  dashboard = true

[log]
  level = "INFO"
EOF

Create dynamic configuration file:

cat > ~/docker_data/traefik/config/dynamic.toml << 'EOF'
[http.middlewares]
  [http.middlewares.securityHeaders.headers]
    browserXssFilter = true
    contentTypeNosniff = true
    frameDeny = true
    sslRedirect = true
    [http.middlewares.securityHeaders.headers.customResponseHeaders]
      X-Robots-Tag = "noindex,nofollow,nosnippet,noarchive,notranslate,noimageindex"
      Server = ""
    [http.middlewares.securityHeaders.headers.referrerPolicy]
      policy = "strict-origin-when-cross-origin"
    [http.middlewares.securityHeaders.headers.contentSecurityPolicy]
      policy = "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; media-src 'self' blob:; connect-src 'self' wss:; frame-src 'self'; base-uri 'self'; form-action 'self'"

[http.routers]
  [http.routers.dashboard]
    rule = "Host(`traefik.your-domain.com`)"
    service = "api@internal"
    entryPoints = ["websecure"]
    middlewares = ["securityHeaders", "traefik-auth"]
    [http.routers.dashboard.tls]
      certResolver = "letsencrypt"

[http.middlewares.traefik-auth.basicAuth]
  # Generate with: echo $(htpasswd -nb username password) | sed -e s/\\$/\\$\\$/g
  users = ["admin:$apr1$randomhash"]  # Replace with your own credentials
EOF

Important: Replace your-email@example.com, traefik.your-domain.com, and the basic auth credentials with your own.

Step 3: Create Docker Compose for Traefik

cat > ~/docker_data/traefik/docker-compose.yml << 'EOF'
version: '3'

services:
  traefik:
    image: traefik:latest
    container_name: traefik
    restart: unless-stopped
    ports:
      - 80:80
      - 443:443
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./config/traefik.toml:/etc/traefik/traefik.toml:ro
      - ./config/dynamic.toml:/etc/traefik/dynamic.toml:ro
      - ./letsencrypt:/letsencrypt
    networks:
      - proxy
    labels:
      - "traefik.enable=true"

networks:
  proxy:
    external: true
    name: proxy
EOF

Step 4: Create Docker Network

docker network create proxy

This creates the network that both Traefik and Jellyfin will use.

Step 5: Start Traefik

cd ~/docker_data/traefik && docker-compose up -d

This starts Traefik in the background.

Step 6: Start Jellyfin

cd ~/docker_data/jellyfin && docker-compose up -d

This starts Jellyfin in the background.

Setting Up Fail2ban for Additional Security (Optional)

Fail2ban can protect against brute force attacks by temporarily banning IP addresses that show malicious behavior.

Step 1: Install Fail2ban

sudo apt install fail2ban -y

Step 2: Configure Fail2ban for Traefik

sudo tee /etc/fail2ban/filter.d/traefik-auth.conf > /dev/null << 'EOF'
[Definition]
failregex = ^.*\s"(GET|POST|HEAD).*401\s.*remote_ip=<HOST>\s.*$
ignoreregex =
EOF

Step 3: Create Jail Configuration

sudo tee /etc/fail2ban/jail.d/traefik.conf > /dev/null << 'EOF'
[traefik-auth]
enabled = true
port = http,https
filter = traefik-auth
logpath = /var/log/traefik/access.log
maxretry = 3
bantime = 3600
EOF

Step 4: Restart Fail2ban

sudo systemctl restart fail2ban

Backup and Restore Procedures

Regular backups are essential for data safety. Here’s how to set up automated backups for Jellyfin.

Backup Script

Create a backup script:

cat > ~/backup-jellyfin.sh << 'EOF'
#!/bin/bash
# Jellyfin backup script

# Set backup directory
BACKUP_DIR="/path/to/backups/jellyfin"
mkdir -p "$BACKUP_DIR"

# Set date format for backup files
DATE=$(date +"%Y-%m-%d")

# Backup Jellyfin configuration
tar -czf "$BACKUP_DIR/jellyfin-config-$DATE.tar.gz" -C ~/docker_data jellyfin/config

# Keep only the last 5 backups
ls -t "$BACKUP_DIR"/*.tar.gz | tail -n +6 | xargs rm -f

echo "Backup completed: $BACKUP_DIR/jellyfin-config-$DATE.tar.gz"
EOF

chmod +x ~/backup-jellyfin.sh

Set Up Automated Weekly Backups

(crontab -l 2>/dev/null; echo "0 2 * * 0 ~/backup-jellyfin.sh") | crontab -

This schedules the backup script to run every Sunday at 2 AM.

Restore Procedure

If you need to restore from a backup:

# Stop containers
cd ~/docker_data/jellyfin && docker-compose down

# Restore from backup
tar -xzf /path/to/backups/jellyfin/jellyfin-config-YYYY-MM-DD.tar.gz -C ~/docker_data/jellyfin

# Start containers
cd ~/docker_data/jellyfin && docker-compose up -d

Common Problems and Solutions

Here are some common issues you might encounter and their solutions.

Transcoding Issues

Problem: Video transcoding is slow or failing.

Diagnosis:

docker logs jellyfin | grep -i transcode

Solution: Enable hardware acceleration if available.

For Intel GPUs:

# Install required packages on host
sudo apt install intel-media-va-driver-non-free -y

# Update docker-compose.yml
# Uncomment the devices section for /dev/dri

Restart Jellyfin:

cd ~/docker_data/jellyfin && docker-compose down && docker-compose up -d

Connection Problems

Problem: Cannot access Jellyfin through domain.

Diagnosis:

# Check if Traefik is running
docker ps | grep traefik

# Check Traefik logs
docker logs traefik

Solution:

# Verify DNS resolution
nslookup jellyfin.your-domain.com

# Check if ports are forwarded correctly
curl -I http://your-domain.com

Docker Container Fails to Start

Problem: Jellyfin container won’t start.

Diagnosis:

docker logs jellyfin

Solution:

# Check permissions
sudo chown -R $(id -u):$(id -g) ~/docker_data/jellyfin

# Verify ports are not in use
sudo netstat -tuln | grep 8096

Maintenance Procedures

Regular maintenance ensures your Jellyfin server remains secure and performs optimally.

Updating Jellyfin

cd ~/docker_data/jellyfin
docker-compose pull
docker-compose down
docker-compose up -d

This pulls the latest image, stops the current container, and starts a new one with the updated image.

Updating Traefik

cd ~/docker_data/traefik
docker-compose pull
docker-compose down
docker-compose up -d

System Updates

sudo apt update && sudo apt upgrade -y

Periodic system updates are essential for security.

Monitoring Disk Space

df -h

Regularly check disk space to ensure sufficient storage for media and container operations.

Conclusion

You now have a fully functional, secure Jellyfin media server running on Ubuntu 20.04 using Docker. The configuration provides:

  1. Isolated container environment for enhanced security
  2. Automatic SSL certificate management with Let’s Encrypt
  3. Reverse proxy with Traefik for secure internet access
  4. Hardened TLS configuration to protect against common vulnerabilities
  5. Backup and restore procedures to prevent data loss
  6. Maintenance instructions for keeping the system updated and secure

By following this guide, you’ve implemented industry best practices for securing a self-hosted media server while maintaining convenient access for authorized users. Remember to regularly update all components and monitor for security advisories related to Docker, Ubuntu, Traefik, and Jellyfin to maintain the security of your system.


Discover more from DIYLABHub.com

Subscribe to get the latest posts sent to your email.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

google.com, pub-5998895780889630, DIRECT, f08c47fec0942fa0