
Streaming your own media shouldn’t mean handing over your library, watch history, and user accounts to another platform. If you want control and predictable performance, you can run Jellyfin on a VPS and keep the stack under your admin keys.
This approach also makes lift-and-shift moves easier when you change providers or regions.
This is an open-source spotlight, but we’ll treat Jellyfin like a service you operate daily. That includes sizing, storage layout, TLS termination, backups, and updates.
The goal isn’t “it works today.” The goal is “it still works next month” after updates and disk growth.
Jellyfin in 2026: what it is, who it’s for, and the license
Jellyfin is a self-hosted media server. It organizes movies, shows, music, and photos. Then it streams them to clients (web, mobile, TV apps, and DLNA). It’s a solid fit if you:
- Keep a home media library and want secure remote access over HTTPS.
- Need to share internal content with a small team (training, demos, recorded calls).
- Want multiple users, profiles, and per-user watch history without data leaving your server.
Jellyfin is open source under the GNU GPLv2. In practice, that means no surprise feature paywalls.
It also means you must follow the license terms if you redistribute modified builds.
How Jellyfin compares to Plex and other hosted media services
Most people look at Jellyfin as a replacement for Plex. It can also replace the “smart TV streaming stick + subscriptions” approach used by hosted services.
- Control: Jellyfin runs on your infrastructure. You don’t need a vendor account for local streaming. Remote access is handled by your reverse proxy and firewall.
- Cost: Jellyfin doesn’t require a subscription for core server features. Your real costs are VPS resources and storage.
- Privacy: Watch history and activity stay on your disk and database. You decide what logs you keep and how long you retain them.
- Ops burden: You own patching, backups, and performance tuning. If you already maintain a Linux VPS, the extra overhead is usually reasonable.
If your priority is minimal admin time, a managed platform may fit better.
If you already run a server for websites or apps, Jellyfin often drops in cleanly.
Before you run Jellyfin on a VPS: sizing, bandwidth, and storage planning
Media servers rarely fail because of CPU first. Storage planning, I/O, and outbound bandwidth limits usually cause the real pain.
Resource requirements (realistic starting points)
- CPU: 2 vCPU is fine for direct-play and light use. Transcoding is CPU-hungry. Plan 4+ vCPU if you expect multiple transcodes.
- RAM: 2 GB works for small libraries. 4 GB is a calmer baseline once the library grows and plugins enter the mix.
- Disk: The OS + Docker + Jellyfin config can fit in 20–30 GB. Your media library is the real variable. Plan it separately and assume growth.
- Network: Remote streaming is often constrained by outbound transfer. A single 1080p stream can be several Mbps, while 4K can be far higher.
To reduce surprises, start with a VPS that has NVMe storage and a sensible outbound transfer policy.
If you host multiple services (WordPress + media server + monitoring), leave RAM headroom. Avoid running everything right at the limit.
For a clean baseline, a HostMyCode VPS is a practical place to run Jellyfin, Nginx, and a couple of supporting services without constant resource pressure.
Storage layout: separate “config” from “media”
Keep Jellyfin’s state and cache separate from your library files. That keeps backups fast. It also helps you avoid snapshotting terabytes every night.
- /opt/jellyfin (small, backed up): Docker Compose, config, metadata, cache policy decisions.
- /srv/media (large): Your movie/show/music folders. Often backed up differently, or replicated less frequently.
On Linux VPSs, it’s common to mount extra volumes under /srv (or /mnt).
If you expect the library to grow, decide early how you’ll scale storage. You can attach a larger volume later, or move the library to object storage with an rclone mount.
That can work, but it adds complexity. It can also slow library scans.
Deployment model: Docker Compose + Nginx reverse proxy
Jellyfin runs well in a container. Docker Compose also keeps updates routine, which matters once people rely on the service.
We’ll deploy:
- Jellyfin container on an internal port (8096).
- Nginx on the host as the public entry point (80/443).
- Let’s Encrypt TLS via Certbot (or your preferred ACME client).
If you prefer Caddy, the same architecture applies.
HostMyCode already has a clean walkthrough for Caddy-based TLS: Set Up Caddy Reverse Proxy for Docker on a VPS.
Docker Compose example for Jellyfin (production-friendly)
Create directories first:
sudo mkdir -p /opt/jellyfin
sudo mkdir -p /opt/jellyfin/config /opt/jellyfin/cache
sudo mkdir -p /srv/media
Create /opt/jellyfin/docker-compose.yml:
services:
jellyfin:
image: jellyfin/jellyfin:latest
container_name: jellyfin
user: "1000:1000"
environment:
- TZ=Etc/UTC
volumes:
- /opt/jellyfin/config:/config
- /opt/jellyfin/cache:/cache
- /srv/media:/media:ro
ports:
- "127.0.0.1:8096:8096"
restart: unless-stopped
Notes that matter once this is running for real:
- Bind to localhost:
127.0.0.1:8096keeps Jellyfin off the public interface. Only Nginx can reach it. - Read-only media mount:
:rohelps prevent accidental deletes from inside the container. If you need write access for uploads, remove:roonly for the specific folders that require it. - User mapping: Set the UID/GID to a real user that owns the config directories. If your main user isn’t 1000, confirm with
id.
Start it:
cd /opt/jellyfin
sudo docker compose up -d
Quick health check:
sudo docker logs --tail=50 jellyfin
Nginx reverse proxy config (with WebSocket support)
Jellyfin uses WebSockets for parts of the UI and playback coordination. Your Nginx config must pass the upgrade headers.
If it doesn’t, some clients will behave strangely.
Create an Nginx server block such as /etc/nginx/sites-available/jellyfin.conf:
server {
listen 80;
server_name media.example.com;
location /.well-known/acme-challenge/ {
root /var/www/html;
}
location / {
return 301 https://$host$request_uri;
}
}
Enable and reload:
sudo ln -s /etc/nginx/sites-available/jellyfin.conf /etc/nginx/sites-enabled/jellyfin.conf
sudo nginx -t
sudo systemctl reload nginx
Then add the TLS server block (we’ll get the cert next).
After certificates exist, your 443 block can look like this:
server {
listen 443 ssl http2;
server_name media.example.com;
ssl_certificate /etc/letsencrypt/live/media.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/media.example.com/privkey.pem;
# Optional hardening headers; keep them conservative for media apps.
add_header X-Content-Type-Options nosniff always;
add_header Referrer-Policy no-referrer-when-downgrade always;
client_max_body_size 0;
location / {
proxy_pass http://127.0.0.1:8096;
proxy_http_version 1.1;
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 Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 3600;
proxy_send_timeout 3600;
}
}
If you want a deeper, up-to-date HTTPS baseline, use HostMyCode’s TLS guide as your reference point. Adapt the ciphers/headers to your environment: Configure HTTPS with Let’s Encrypt on a VPS (Nginx or Apache).
Let’s Encrypt certificates (Certbot) and DNS prerequisites
First, point media.example.com to your server’s public IP.
If you manage domains for client sites, keep DNS changes and renewals organized. HostMyCode domain management can simplify that: HostMyCode domains.
On Ubuntu/Debian, install Certbot and the Nginx plugin:
sudo apt update
sudo apt install -y certbot python3-certbot-nginx
Issue the cert:
sudo certbot --nginx -d media.example.com
Then reload Nginx and verify auto-renewal:
sudo nginx -t && sudo systemctl reload nginx
sudo certbot renew --dry-run
Practical pitfall: if you’re using a firewall, make sure ports 80 and 443 are open.
If you need a baseline, HostMyCode’s UFW guide is a quick refresher: Set Up UFW Firewall on Ubuntu for a Web Server.
Security and access control choices that actually matter
Jellyfin is a login-based app, but exposure still matters. Most setups fall into one of these patterns:
- Public HTTPS + strong passwords: simplest. Add fail2ban if you see brute-force attempts.
- Public HTTPS + additional Nginx auth: adds a second gate, but can break some clients that don’t handle Basic Auth well.
- Private access only: require VPN (WireGuard) and keep Jellyfin off the public internet.
If you already use a VPN for admin work, private-only access is usually the cleanest option.
HostMyCode has a practical WireGuard setup guide you can reuse: Set Up WireGuard VPN on Ubuntu VPS for Admin Access.
Backups: what to save (and what not to bother with)
Backups for a media server are really two different jobs:
- App state: Jellyfin config, database, metadata, users.
- Media library: the large files. You may already store the originals elsewhere.
Back up Jellyfin state
At minimum, back up /opt/jellyfin/config. That’s where your server configuration and database live.
A simple backup job (tar + compression) is often enough:
sudo systemctl stop docker
sudo tar -C /opt/jellyfin -czf /root/jellyfin-config-$(date +%F).tar.gz config
sudo systemctl start docker
Stopping Docker is conservative. You can also leave it running and accept a small risk of an inconsistent snapshot.
If you want tighter integrity, stop only the container during a short maintenance window:
sudo docker stop jellyfin
sudo tar -C /opt/jellyfin -czf /root/jellyfin-config-$(date +%F).tar.gz config
sudo docker start jellyfin
Back up media intelligently
If your media files are your only copy, you need a real strategy. Plan for offsite replication, retention, and restore testing.
If the files already live on a NAS or cold storage, you can often back up only Jellyfin state. After a rebuild, you re-point the library path.
If you want the “this VPS is rebuildable from scratch” approach, write down a recovery plan and keep it current. HostMyCode’s disaster recovery runbook is a solid template: Disaster recovery tutorial (2026): Build a tested VPS recovery plan.
Updates: keep them boring and reversible
Docker makes updates easy, but not risk-free. Stick to a routine you can repeat every time:
- Back up
/opt/jellyfin/config(or snapshot your volume). - Pull new images.
- Restart the stack.
- Check logs and test playback on one client.
cd /opt/jellyfin
sudo docker compose pull
sudo docker compose up -d
sudo docker logs --tail=50 jellyfin
For OS security patches, enable unattended upgrades on Ubuntu servers that aren’t heavily customized. Jellyfin won’t care. You also reduce exposure to known CVEs.
If you want a quick setup reference: Set Up Automated Security Updates on Ubuntu VPS.
Operational checks: the small things that prevent midnight debugging
After Jellyfin is online, run a few quick checks. They tend to catch problems that show up later.
- Nginx access/error logs: confirm client IPs show up correctly (look for
X-Forwarded-Forbehavior). - Disk usage: metadata and thumbnails grow over time. Watch
/opt/jellyfinand set alert thresholds. - Library scan time: slow scans often point to I/O limits or remote mounts that don’t like lots of small reads.
- Transcode temp location: if you transcode, make sure the temp directory isn’t sitting on a tiny root disk.
Basic monitoring pays off. Even lightweight checks for CPU, RAM, disk fill, and load average can warn you before playback starts buffering.
If you want a full alerting approach for a VPS, this guide maps it out well: Server Monitoring Setup Guide Tutorial (2026).
When a VPS isn’t enough: knowing when to move to a dedicated server
Some Jellyfin installs outgrow a VPS for reasons that have nothing to do with Jellyfin itself:
- You need large local storage (multi-terabyte NVMe/SATA arrays).
- Multiple users transcode at once and CPU saturation becomes normal.
- You want more predictable I/O than a virtualized disk can deliver under load.
At that point, a dedicated box can be simpler than juggling external volumes and constant tuning.
For steady media traffic (or a combined workload server that also runs sites and databases), look at HostMyCode dedicated servers. Size it around storage and transcoding first, not just vCPU count.
Summary: a clean Jellyfin stack you can actually operate
A maintainable Jellyfin setup follows a familiar pattern: Docker Compose for repeatable deployments, Nginx as the single front door, Let’s Encrypt for TLS, and backups that focus on state instead of blindly archiving terabytes.
Once that base is in place, Jellyfin behaves like any other small service you host. The difference is heavier storage and bandwidth needs.
If you plan to run Jellyfin on a VPS alongside other workloads, buy RAM headroom and fast disk up front.
Then treat updates and backups as routine work, not occasional emergencies.
A stable server feels boring. That’s exactly what you want.
Need a reliable place to host Jellyfin without dealing with slow storage or tight memory? Start with a HostMyCode VPS for a clean Docker + Nginx setup, and move to managed VPS hosting if you’d rather hand off routine maintenance.
FAQ
Can I run Jellyfin behind HTTPS without exposing port 8096?
Yes. Bind Jellyfin to 127.0.0.1:8096 in Docker Compose and proxy it through Nginx on 443. Clients reach only Nginx.
Do I need a GPU for Jellyfin on a VPS?
Not for direct-play. A GPU helps with hardware transcoding, but many VPS plans don’t expose a usable GPU. If you expect frequent transcoding, prioritize CPU headroom and consider pre-encoding media to your common device formats.
What should I back up if my media files already exist elsewhere?
Back up /opt/jellyfin/config (and your Compose file). That preserves users, watch history, libraries, and settings. You can reattach the media path after a rebuild.
Why do some clients fail if I add Nginx Basic Auth?
Some TV and mobile clients don’t handle extra auth prompts consistently. If you want an extra security layer, a VPN (WireGuard) is usually less fragile than Basic Auth in front of the app.
What’s the fastest way to move Jellyfin to a new server?
Restore /opt/jellyfin/config on the new host, keep the same media path layout (/srv/media), then start the stack. If you also need a DNS cutover plan, use this approach: DNS cutover with low TTL and parallel hosting.