This shows you the differences between two versions of the page.
| Next revision | Previous revision | ||
| computing:jellyfin [2025/11/30 17:56] – created oemb1905 | computing:jellyfin [2025/11/30 18:37] (current) – oemb1905 | ||
|---|---|---|---|
| Line 11: | Line 11: | ||
| ------------------------------------------- | ------------------------------------------- | ||
| - | This tutorial is for Debian Trixie users seeking to set up a production-ready Jellyfin server | + | Latest Updates: https:// |
| - | (forthcoming) | + | This tutorial is for Debian users wanting to set up a production-ready Jellyfin server. This instance runs on a VM inside my virsh+qemu stack recently recapped in detail in this article. This VM is set up with a LAMP stack, a reverse proxy with Let's Encrypt, automated syncing, scanning, and some hardening measures. I'm using fpm with the mpm_event handler for concurrency. My standard fail2ban setup is in place for protection. This instance is designed for private media watching. The library is imaged off my master library via a remote source and includes aggressive cover art fetching. Don't proceed before TLS/LAMP stack is in place. If you need help, visit https:// |
| - | --- // | + | First, we need to install the required packages: |
| + | |||
| + | sudo apt update && sudo apt upgrade -y | ||
| + | sudo apt install ffmpeg wget nano curl snapd ufw fail2ban postfix apache2 php8.4-fpm php8.4-mysql php8.4-curl php8.4-gd php8.4-mbstring php8.4-xml php8.4-zip apt-transport-https ca-certificates gnupg | ||
| + | sudo a2enmod ssl headers | ||
| + | |||
| + | Once your LAMP stack is installed, edit your 000-default.conf host for your Jellyfin domain. After that, cut it a certificate before proceeding: | ||
| + | |||
| + | sudo apt install certbot letsencrypt python3-certbot-apache | ||
| + | sudo certbot --authenticator standalone --installer apache -d gnulinux.media --pre-hook " | ||
| + | |||
| + | Later, we will switch the 000-default.conf virtual host to use a reverse proxy, but for now, it helps to create the certificate with the mostly stock configuration. Now, let's install Jellyfin: | ||
| + | |||
| + | sudo mkdir -p / | ||
| + | curl -fsSL https:// | ||
| + | echo "deb [arch=$( dpkg --print-architecture ) signed-by=/ | ||
| + | sudo apt update | ||
| + | sudo apt install -y jellyfin | ||
| + | sudo systemctl enable jellyfin | ||
| + | sudo systemctl start jellyfin | ||
| + | |||
| + | Once Jellyfin is installed and running, let's create the media directory and set permissions: | ||
| + | |||
| + | sudo mkdir -p / | ||
| + | sudo chown -R jellyfin: | ||
| + | sudo find / | ||
| + | sudo find / | ||
| + | |||
| + | Now that Jellyfin is installed and has a properly setup media directory, we can prepare and configure apache and php for the reverse proxy. | ||
| + | |||
| + | sudo a2enmod proxy proxy_http rewrite proxy_fcgi setenvif sudo a2enconf php8.4-fpm | ||
| + | sudo a2dismod mpm_prefork php8.4 | ||
| + | sudo a2enmod mpm_event | ||
| + | |||
| + | Now that the modules that the reverse proxy requires are enabled, we can safely swap out the contents of 000-default.conf and the auto-generated 000-default-le.conf. Enter something like this in the http virtual host: | ||
| + | |||
| + | <code bash> | ||
| + | < | ||
| + | |||
| + | ServerName gnulinux.media | ||
| + | |||
| + | ProxyPreserveHost On | ||
| + | ProxyPass "/ | ||
| + | ProxyPass "/ | ||
| + | ProxyPassReverse "/ | ||
| + | ProxyPass "/" | ||
| + | ProxyPassReverse "/" | ||
| + | |||
| + | RequestHeader set X-Forwarded-Proto " | ||
| + | |||
| + | ErrorLog ${APACHE_LOG_DIR}/ | ||
| + | CustomLog ${APACHE_LOG_DIR}/ | ||
| + | |||
| + | RewriteEngine on | ||
| + | RewriteCond %{SERVER_NAME} =gnulinux.media | ||
| + | RewriteRule ^ https:// | ||
| + | |||
| + | < | ||
| + | SetHandler " | ||
| + | </ | ||
| + | |||
| + | </ | ||
| + | </ | ||
| + | |||
| + | And, for the https virtual host, something like: | ||
| + | |||
| + | <code bash> | ||
| + | < | ||
| + | |||
| + | ServerName gnulinux.media | ||
| + | |||
| + | ProxyPreserveHost On | ||
| + | ProxyPass "/ | ||
| + | ProxyPass "/ | ||
| + | ProxyPassReverse "/ | ||
| + | ProxyPass "/" | ||
| + | ProxyPassReverse "/" | ||
| + | |||
| + | RequestHeader set X-Forwarded-Proto " | ||
| + | |||
| + | SSLEngine on | ||
| + | |||
| + | ErrorLog ${APACHE_LOG_DIR}/ | ||
| + | CustomLog ${APACHE_LOG_DIR}/ | ||
| + | |||
| + | SSLCertificateFile / | ||
| + | SSLCertificateKeyFile / | ||
| + | Include / | ||
| + | |||
| + | < | ||
| + | SetHandler " | ||
| + | </ | ||
| + | |||
| + | </ | ||
| + | </ | ||
| + | |||
| + | Since we created the Let's Encrypt certs on the stock configuration, | ||
| + | |||
| + | sudo apache2ctl configtest | ||
| + | sudo systemctl restart apache2 | ||
| + | |||
| + | Make sure you've run ss -tulpn and that you are only listening on ports with services you intend and desire to be on this instance. Don't proceed if you have rogue services listening. Once that's verified, you can optionally add a firewall on top for extra coverage: | ||
| + | |||
| + | sudo ufw allow 80 | ||
| + | sudo ufw allow 443 | ||
| + | sudo ufw allow 22 | ||
| + | sudo ufw enable | ||
| + | |||
| + | Now, although this instance is only for family-based watching and viewing, not public, I still want to tweak mpm_event and fpm to be snappy. After all, I want the family' | ||
| + | |||
| + | Adjust mpm_event for 8 cores and 400 workers. Again, overkill, but it certainly won't hurt anything. Head over to nano / | ||
| + | |||
| + | StartServers 4 | ||
| + | MinSpareThreads 25 | ||
| + | MaxSpareThreads 75 | ||
| + | ThreadLimit 64 | ||
| + | ThreadsPerChild 25 | ||
| + | MaxRequestWorkers 400 | ||
| + | MaxConnectionsPerChild 0 | ||
| + | ServerLimit 16 | ||
| + | |||
| + | Again, I see lots of folks that still use apache pre-fork and, almost as bad, those that use mpm_event instead, but forget that it requires configuring to be usable by more than a handful of clients. Let's open up nano / | ||
| + | |||
| + | pm = dynamic | ||
| + | pm.max_children = 200 | ||
| + | pm.start_servers = 20 | ||
| + | pm.min_spare_servers = 10 | ||
| + | pm.max_spare_servers = 20 | ||
| + | pm.max_requests = 1000 | ||
| + | request_terminate_timeout = 300s | ||
| + | |||
| + | Let's test the configuration and then restart the services: | ||
| + | |||
| + | sudo php-fpm8.4 -t | ||
| + | sudo systemctl restart php8.4-fpm apache2 | ||
| + | |||
| + | The rest of the setup is conducted on the web panel. So, navigate to your isntance, e.g., https:// | ||
| + | |||
| + | <code bash> | ||
| + | # | ||
| + | #timer | ||
| + | DATE=`date +" | ||
| + | START0=" | ||
| + | |||
| + | #begin script | ||
| + | touch / | ||
| + | touch / | ||
| + | echo " | ||
| + | # remove --delete because it will zap the cover art Jellyfin gets you | ||
| + | sudo rsync -ai --log-file=/ | ||
| + | sudo rsync -ai --log-file=/ | ||
| + | sudo rsync -ai --log-file=/ | ||
| + | |||
| + | echo " | ||
| + | #end script | ||
| + | |||
| + | # end timer | ||
| + | END0=" | ||
| + | DURATION0=$[ ${END0} - ${START0} ] | ||
| + | MINUTES0=$[ ${DURATION0} / 60 ] | ||
| + | |||
| + | #email report | ||
| + | sed -i " | ||
| + | #only appends, not prepends | ||
| + | / | ||
| + | |||
| + | rm / | ||
| + | rm / | ||
| + | </ | ||
| + | |||
| + | Again, this script runs daily and/or manually as needed. Sometimes I setup custom jails for fail2ban, but I am leaving this one stock for now. I think regular fail2ban with apache' | ||
| + | |||
| + | --- // | ||