Self-Host Jellyfin with Caddy on Windows or Linux
This guide sets up Jellyfin on a Windows or Linux server, gives it a hostname that can follow a changing public IP address, and places Caddy in front to provide HTTPS. The examples keep Jellyfin’s HTTP port private and expose only Caddy to the internet.
You need a server that stays online, administrator access, a media folder, and control of your router or firewall. You can use a domain you own or a No-IP hostname. A dynamic DNS client updates a hostname when your address changes; it does not create public reachability through carrier-grade NAT (CGNAT).
Install Jellyfin for Your Operating System
Application SetupChoose one installation method. Use the official Windows installer for Windows, the official Debian/Ubuntu installation procedure for those distributions, or the official Jellyfin container on another Linux distribution. The container example binds port 8096 to the server’s loopback interface so Caddy on that same host can reach it without publishing Jellyfin directly.
1. Download the current stable installer from https://jellyfin.org/downloads/.2. Run the installer as an administrator and complete the setup.3. If offered, enable the Jellyfin Windows service for unattended startup.4. Open http://127.0.0.1:8096 locally and confirm the setup wizard loads.sudo apt updatesudo apt install -y curl coreutils
# Download the script and the checksum file published beside it.curl -fsSLO https://repo.jellyfin.org/install-debuntu.shcurl -fsSLO https://repo.jellyfin.org/install-debuntu.sh.sha256sum
# Stop if the downloaded script does not match Jellyfin's checksum.sha256sum -c install-debuntu.sh.sha256sum
# Continue only when the checksum reports OK.sudo bash install-debuntu.shsudo systemctl enable --now jellyfinsystemctl status jellyfin --no-pager# Change these host paths to locations on your Linux server.sudo mkdir -p /srv/jellyfin/config /srv/jellyfin/cachesudo chown -R 1000:1000 /srv/jellyfin
docker run -d \ --name jellyfin \ --user 1000:1000 \ --publish 127.0.0.1:8096:8096 \ --volume /srv/jellyfin/config:/config \ --volume /srv/jellyfin/cache:/cache \ --volume /srv/media:/media:ro \ --restart unless-stopped \ jellyfin/jellyfin
docker ps --filter name=jellyfin❯ View Expected Console Output
Open http://127.0.0.1:8096 on the server. For the container example, ensure the account running Jellyfin can read the media directory; replace UID/GID 1000:1000 if needed.
Create an Admin User and Add the Media Library
Initial ConfigurationComplete Jellyfin’s browser-based wizard on the server or a trusted device on the same LAN. For a native install, a LAN device can browse to http://<server-LAN-IP>:8096. The container example is loopback-only, so open its wizard on the server itself or use an SSH port forward. Create a unique administrator account, add the media paths, choose your preferred metadata settings, and verify that a test item plays locally before configuring public access.
On Windows, grant the Jellyfin service account read access to the media folders. On Linux, check ownership and permissions for both the media and configuration paths. The Docker example uses read-only access for /media; remove :ro only if Jellyfin must write to that mount.
On the server: http://127.0.0.1:8096From LAN: http://<server-LAN-IP>:8096 (native install only)Example library: /srv/media/Movies (Linux)Example library: D:\Media\Movies (Windows)❯ View Expected Console Output
Confirm that the library appears and at least one item plays from a device on your home network. Continue only after local access works.
Point a Domain or No-IP Hostname at Your Server
DNS and Dynamic DNSCaddy needs a public hostname for automatic certificate issuance. With a domain you own, create an A record for the server’s current public IPv4 address. Add an AAAA record only when the server has globally reachable IPv6 and your IPv6 firewall allows the required traffic. If your ISP changes your IPv4 address, use your router’s dynamic DNS client or run one updater for your hostname.
With No-IP, create a hostname such as media-room.ddns.net, then configure a No-IP Dynamic Update Client (DUC) or your router to update it. Use a dedicated DDNS Key where the client supports it, keep its credentials private, and configure only one updater for each hostname. See No-IP’s guides for the Windows DUC or Linux DUC.
DNS record: AName: mediaValue: your current public IPv4 addressResult: media.example.net
Optional IPv6 record, only if publicly reachable:DNS record: AAAAName: mediaValue: the server's globally routable IPv6 address1. Create a hostname in your No-IP account and generate a DDNS Key.2. Download and install the current Windows DUC from No-IP.3. Sign in, select the hostname, and confirm updates; use a DDNS Key if supported by the client.4. Enable the DUC system service if the installer offers that option.5. Check the DUC status and confirm the hostname resolves to your public IP.# Download and extract the current DUC archive. Replace the version and# package architecture below with the names in No-IP's current instructions.wget --content-disposition https://www.noip.com/download/linux/latesttar xf noip-duc_3.3.0.tar.gzcd noip-duc_3.3.0/binariessudo apt install ./noip-duc_3.3.0_amd64.debcd ..
# Install No-IP's systemd unit and add the DDNS Key credentials/hostname.sudo cp debian/service /etc/systemd/system/noip-duc.servicesudoedit /etc/default/noip-ducIn /etc/default/noip-duc, add the DDNS Key username and password and the hostname to update:
NOIP_USERNAME=your_ddns_key_usernameNOIP_PASSWORD=your_ddns_key_passwordNOIP_HOSTNAMES=media-room.ddns.netThen save the file, restrict its permissions, and enable the service:
sudo chmod 0600 /etc/default/noip-ducsudo systemctl daemon-reloadsudo systemctl enable --now noip-ducsystemctl status noip-duc --no-pagerKeep that file private because it contains the update credentials.
1. Open the router's Dynamic DNS page.2. Select No-IP and enter the hostname and DDNS Key credentials.3. Save, then check that the router reports a successful update.❯ View Expected Console Output
From another network or a public DNS lookup, confirm the hostname returns your current public address before continuing. Some free No-IP hostnames require periodic confirmation; follow the reminder from No-IP to keep the hostname active.
Forward Ports 80 and 443 to the Caddy Host
Network AccessReserve a stable LAN address for the server, then forward inbound TCP ports 80 and 443 on the router to that address. Allow those ports through the server firewall. Port 80 supports HTTP validation and redirects; port 443 serves HTTPS. Do not forward Jellyfin’s port 8096.
If your router’s WAN address differs from the public address shown by an IP-check service, your ISP may be using CGNAT. Ask the ISP for a public address or use a private access method such as a VPN; ordinary port forwarding and dynamic DNS cannot make a CGNAT connection publicly reachable.
TCP 80 -> <server-LAN-IP>:80TCP 443 -> <server-LAN-IP>:443
Do not add a port-forward for TCP 8096.sudo ufw allow 80/tcpsudo ufw allow 443/tcpsudo ufw statussudo firewall-cmd --permanent --add-service=httpsudo firewall-cmd --permanent --add-service=httpssudo firewall-cmd --reloadsudo firewall-cmd --list-servicesNew-NetFirewallRule -DisplayName 'Caddy HTTP' -Direction Inbound -Action Allow -Protocol TCP -LocalPort 80New-NetFirewallRule -DisplayName 'Caddy HTTPS' -Direction Inbound -Action Allow -Protocol TCP -LocalPort 443Get-NetFirewallRule -DisplayName 'Caddy HTTP','Caddy HTTPS'❯ View Expected Console Output
Keep the router forwards pointed at the reserved LAN address. If your server’s LAN address changes, the forwards may silently reach the wrong device.
Add Caddy to Jellyfin's Known Proxies
Forwarded HeadersJellyfin uses forwarded headers to recognize the original client and HTTPS scheme. In the Jellyfin dashboard, open Networking, add the address of the Caddy host to Known Proxies, and save. When Caddy and Jellyfin run on the same host with the configuration below, start with 127.0.0.1. Do not trust every proxy with a broad address range.
For a container on a custom Docker network, Jellyfin may see the Docker bridge gateway or another container address instead of 127.0.0.1. Inspect the container network and use the actual source address Jellyfin sees. The loopback-published container in Step 1 is accessed by host Caddy through 127.0.0.1:8096; still confirm the proxy source in Jellyfin’s logs if forwarded client details are not recognized.
Jellyfin Dashboard -> Administration -> Networking -> Known Proxies -> Add: 127.0.0.1 -> Save❯ View Expected Console Output
Restart Jellyfin if the setting prompts for it. Keep Jellyfin’s HTTP listener available only on the local host or trusted LAN; the router should expose Caddy, not port 8096.
Install the Caddy Web Server
Caddy SetupInstall Caddy using its official package repository on Linux or the official Windows binary. The Linux packages register a systemd service. On Windows, place the binary and configuration in a stable directory; the next step registers it as a service with WinSW.
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curlcurl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpgcurl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.listsudo chmod o+r /usr/share/keyrings/caddy-stable-archive-keyring.gpgsudo chmod o+r /etc/apt/sources.list.d/caddy-stable.listsudo apt updatesudo apt install caddysudo dnf install 'dnf5-plugins' || sudo dnf install 'dnf-plugins-core'sudo dnf copr enable @caddy/caddysudo dnf install caddysudo dnf install 'dnf-plugins-core'sudo dnf copr enable @caddy/caddysudo dnf install caddy# Download the current Windows Caddy binary from the official download page.New-Item -ItemType Directory -Force C:\Caddy | Out-Null# Place caddy.exe in C:\Caddy and confirm it runs:Set-Location C:\Caddy./caddy.exe version❯ View Expected Console Output
On Linux, the service is named caddy. Run Caddy as a service so it starts after a reboot and can bind to ports 80 and 443.
Write and Validate the Caddyfile
Reverse ProxyReplace media-room.ddns.net with the domain or No-IP hostname from Step 3. The same minimal Caddyfile works on Windows and Linux and sends requests to Jellyfin’s local HTTP listener. Caddy handles the public TLS connection and renews its certificate automatically when DNS and port reachability are correct.
media-room.ddns.net { reverse_proxy 127.0.0.1:8096}sudoedit /etc/caddy/Caddyfilesudo caddy validate --config /etc/caddy/CaddyfileSet-Location C:\Caddynotepad .\Caddyfile./caddy.exe validate --config .\Caddyfile❯ View Expected Console Output
A successful validation reports that the configuration is valid. Confirm the hostname is spelled exactly as it appears in public DNS.
Run Caddy as a Service and Test from Outside
Launch and VerificationStart the Linux service after validating the configuration. On Windows, download the current WinSW executable for your system from its official releases, place it beside Caddy, and rename it as shown below so it can register Caddy as a service that survives sign-out and reboots. Test from a phone with Wi-Fi disabled or another external network; testing only from the server does not verify router forwarding or public DNS.
sudo systemctl enable --now caddysudo systemctl reload caddysystemctl status caddy --no-pagersudo journalctl -u caddy -n 50 --no-pager# Put the WinSW executable beside Caddy and rename it caddy-service.exe.# Save the following XML as C:\Caddy\caddy-service.xml:@'<service> <id>Caddy</id> <name>Caddy</name> <description>Caddy web server and reverse proxy</description> <executable>%BASE%\caddy.exe</executable> <arguments>run --config "%BASE%\Caddyfile"</arguments> <workingdirectory>%BASE%</workingdirectory> <startmode>Automatic</startmode> <onfailure action="restart" delay="10 sec" /></service>'@ | Set-Content -Encoding utf8 C:\Caddy\caddy-service.xml
Set-Location C:\Caddy./caddy.exe validate --config .\Caddyfile./caddy-service.exe install./caddy-service.exe start./caddy-service.exe statusGet-Service Caddy❯ View Expected Console Output
Browse to https://media-room.ddns.net from outside your LAN. The browser should show a trusted certificate and Jellyfin’s sign-in page. If it fails, check DNS resolution, the public/WAN address, router forwards, host firewall, and Caddy logs before changing Jellyfin’s port.

Figure 1: Jellyfin’s sign-in page reached through the public HTTPS hostname after Caddy’s TLS setup.
Official References
- Jellyfin installation for Windows
- Jellyfin installation for Linux
- Jellyfin container installation
- Jellyfin reverse proxy and Known Proxies
- Caddy installation
- Caddy automatic HTTPS requirements
- No-IP Dynamic Update Client for Windows
- No-IP Dynamic Update Client for Linux
- Run the No-IP Linux DUC at startup
- No-IP DDNS Keys
- WinSW releases