This guide explains how to set up an advanced Nginx reverse proxy on Ubuntu Server 24.04 for Jellyfin. It includes securing the site with Let’s Encrypt SSL certificates, adding headers, enabling multiple domains, and optionally integrating with Cloudflare Tunnel. All corrections made during testing are included to avoid common pitfalls.
Ubuntu Server 24.04 with sudo access
Public domain name (example: jelly.keepittechie.com)
DNS record pointing to your server’s public IP
Jellyfin server running separately (e.g., 10.10.0.50:8096)
Firewall open for ports 80 and 443
sudo apt update
sudo apt install -y nginx certbot python3-certbot-nginx
sudo systemctl enable --now nginx
sudo ufw allow 'Nginx Full'
Verify:
nginx -t
systemctl status nginx
sudo mkdir -p /var/www/letsencrypt/.well-known/acme-challenge
A map directive must be declared in the http block, not inside a server block. Create a separate file:
sudo tee /etc/nginx/conf.d/upgrade_map.conf >/dev/null <<'EOF'
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
EOF
sudo tee /etc/nginx/snippets/security-headers.conf >/dev/null <<'EOF'
add_header X-Content-Type-Options nosniff;
add_header X-Frame-Options SAMEORIGIN;
add_header Referrer-Policy strict-origin-when-cross-origin;
add_header X-XSS-Protection "1; mode=block";
EOF
sudo tee /etc/nginx/snippets/proxy.conf >/dev/null <<'EOF'
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 $connection_upgrade;
proxy_connect_timeout 10s;
proxy_send_timeout 120s;
proxy_read_timeout 120s;
send_timeout 120s;
EOF
Create a new file for Jellyfin:
sudo tee /etc/nginx/sites-available/jelly.conf >/dev/null <<'EOF'
server {
listen 80;
listen [::]:80;
server_name jelly.keepittechie.com;
location /.well-known/acme-challenge/ {
root /var/www/letsencrypt;
allow all;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name jelly.keepittechie.com;
ssl_certificate /etc/letsencrypt/live/jelly.keepittechie.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/jelly.keepittechie.com/privkey.pem;
include /etc/nginx/snippets/security-headers.conf;
include /etc/nginx/snippets/proxy.conf;
location / {
proxy_pass http://10.10.0.50:8096;
proxy_redirect off;
proxy_buffering off;
}
access_log /var/log/nginx/jelly.access.log;
error_log /var/log/nginx/jelly.error.log warn;
}
EOF
Enable the site:
sudo ln -s /etc/nginx/sites-available/jelly.conf /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d jelly.keepittechie.com --email [email protected] --agree-tos --no-eff-email
sudo apt install -y python3-certbot-dns-cloudflare
sudo mkdir -p /root/.secrets
sudo tee /root/.secrets/cloudflare.ini >/dev/null <<EOF
dns_cloudflare_api_token = YOUR_API_TOKEN
EOF
sudo chmod 600 /root/.secrets/cloudflare.ini
sudo certbot certonly \
--dns-cloudflare \
--dns-cloudflare-credentials /root/.secrets/cloudflare.ini \
--dns-cloudflare-propagation-seconds 60 \
-d jelly.keepittechie.com \
--email [email protected] --agree-tos --no-eff-email
Certbot installs a systemd timer by default. Verify:
systemctl list-timers | grep certbot
Test renewal:
sudo certbot renew --dry-run
If Nginx does not reload automatically after renewal, add a deploy hook:
sudo tee /etc/letsencrypt/renewal-hooks/deploy/nginx-reload.sh >/dev/null <<'EOF'
#!/bin/bash
systemctl reload nginx
EOF
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/nginx-reload.sh
502 Bad Gateway
Check upstream IP and port. Ensure the Jellyfin container is running and accessible.
Location directive not allowed
Ensure location blocks are inside a server { } block. The map directive must be in http context, not inside a server block.
Unauthorized certificate request
If using Cloudflare, disable proxy (gray-cloud) or switch to DNS-01 validation.
Certbot flags not recognized
Install the correct plugin: sudo apt install -y python3-certbot-dns-cloudflare.
If you prefer not to expose ports 80 and 443, you can use Cloudflare Tunnel (cloudflared) to route traffic securely to your Nginx proxy. Configure /etc/cloudflared/config.yml with your tunnel ID and map jelly.keepittechie.com to https://localhost:443.