LetsEncrypt with Certbot
LetsEncrypt is a service that provides free SSL/TLS certificates to users. Certbot is a client that makes this easy to accomplish and automate. In addition, it has plugins for Apache and Nginx that make automating certificate generation even easier.
Installation instructions for most Linux distributions can be found on the Certbot website.
Once the packages are installed, you're ready to generate a new certificate.
Apache
Certbot Apache Plugin
After installing Certbot and the Apache plugin, certificate generation is accomplished by with the following command.
certbot certonly --apache --noninteractive --agree-tos --email YOUR_EMAIL -d DOMAIN_NAME
Update the 'SSLCertificateFile' and 'SSLCertificateKeyFile' sections, then restart the service.
Add a job to cron so the certificate will be renewed automatically.
echo "0 0 * * * root certbot renew --quiet --no-self-upgrade --post-hook 'systemctl reload apache2'" | sudo tee -a /etc/cron.d/renew_certbot
Certbot Webroot
Debian
If the certbot apache plugin doesn't work with your config, use webroot instead.
Add the following to your
Run the certbot command as root: HAProxy doesn't currently have a Certbot plugin. To get around this, run Certbot in standalone mode and proxy traffic through your network. Enable the frontend and backend in the config above, and then run Certbot. The port can be changed to anything you like, but be sure that the HAProxy config and your Certbot command match. HAProxy needs to have the certificate and key files concatenated into the same file to read it correctly. This can be accomplished with the following command. Uncomment Place the following script in Make sure the script is executable. Add a job to cron so the certificate will be renewed automatically. After installing Certbot and the Nginx plugin with Note: For Fedora Linux distributions (e.g. CentOS 8) use Add the Copy and paste the whole Nginx sample configuration file from above, changing the parameters according to your setup and uncommenting the lines. Add a job to cron so the certificate will be renewed automatically. This section assumes that Jellyfin is running in a Docker container (on Linux). This section also assumes that you wish to run Let's Encrypt in a Docker container as well. The Linuxserver/swag Docker container has a built-in nginx webserver to handle the reverse proxy. Linuxserver/letsencrypt is deprecated in favor of linuxserver/swag, see here for information on how to migrate if needed. First, you need to determine a few things. List of DNS Plugins here if using DNS-01 challenge. Then, depending on what those settings are, you'll need to adjust the values below as needed. For example, the docker create command from the LinuxServer team for the Swag Docker container: Assuming I follow this template and adjust for my region, ports, and path, it would look like this (with personal information redacted): This will pull down the linuxserver/letsencrypt container, and then create it with the variables specified. You'll then want to start the docker container with At this point, navigate to what volume you selected (in my example, it's The one we're interested in for jellyfin is It should look like this (this file is The lines we're interested in is Then, within Jellyfin settings (Dashboard -> Networking), scroll down to "Public HTTP port number" and "Public HTTPS port number", and make sure HTTP Port number is 8096, while HTTPS port number is 8920. Restart your Let's Encrypt docker container by running DocumentRoot /var/www/html/
#Do not pass the .well-known directory when using certbot and webroot
ProxyPass /.well-known !
sudo certbot certonly --webroot -w /var/www/html --agree-tos --email YOUR_EMAIL -d DOMAIN_NAME
HAProxy
certbot certonly --standalone --preferred-challenges http-01 --http-01-port 8888 --noninteractive --agree-tos --email YOUR_EMAIL -d DOMAIN_NAME
cat /etc/letsencrypt/live/DOMAIN_NAME/fullchain.pem /etc/letsencrypt/live/DOMAIN_NAME/privkey.pem > /etc/ssl/DOMAIN_NAME.pem
bind *:443
and the redirect section in the configuration, then reload the service.Automatic Certificate Renewal
/usr/local/bin/
to automatically update your SSL certificate.SITE=DOMAIN_NAME
# move to the correct let's encrypt directory
cd /etc/letsencrypt/live/$SITE
# cat files to make combined .pem for haproxy
cat fullchain.pem privkey.pem > /etc/ssl/$SITE.pem
# reload haproxy
service haproxy reload
chmod u+x /usr/local/bin/letsencrypt-renew.sh
@monthly /usr/bin/certbot renew --renew-hook "/usr/local/bin/letsencrypt-renew.sh" >> /var/log/letsencrypt-renewal.log
Nginx
sudo apt install certbot python3-certbot-nginx
, generate the certificate.sudo dnf install python3-certbot-nginx
to install the Nginx plugin.sudo certbot --nginx --agree-tos --redirect --hsts --staple-ocsp --email YOUR_EMAIL -d DOMAIN_NAME
--rsa-key-size 4096
parameter if you want a 4096 bit key instead.echo "0 0 * * * root certbot renew --quiet --no-self-upgrade --post-hook 'systemctl reload nginx'" | sudo tee -a /etc/cron.d/renew_certbot
Let's Encrypt and Docker
id
(replacing "user" with the username of the user the container will be running as)docker create \
--name=swag \
--cap-add=NET_ADMIN \
-e PUID=1000 \
-e PGID=1000 \
-e TZ=Europe/London \
-e URL=example.com \
-e SUBDOMAINS=www, \
-e VALIDATION=http \
-e DNSPLUGIN=cloudflare `#optional` \
-e DUCKDNSTOKEN=<token> `#optional` \
-e EMAIL=<e-mail> `#optional` \
-e DHLEVEL=2048 `#optional` \
-e ONLY_SUBDOMAINS=false `#optional` \
-e EXTRA_DOMAINS=<extradomains> `#optional` \
-e STAGING=false `#optional` \
-p 443:443 \
-p 80:80 `#optional` \
-v </path/to/appdata/config>:/config \
--restart unless-stopped \
linuxserver/swag
docker create --name=swag --cap-add=NET_ADMIN -e PUID=1000 -e PGID=1000 -e TZ=America/Chicago -e URL=example.com -e SUBDOMAINS=jellyfin -e VALIDATION=http -e EMAIL=email@email.com -e DHLEVEL=2048 -e ONLY_SUBDOMAINS=false -e STAGING=false -p 443:443 -p 80:80 -v /path/to/appdata/swag/:/config --restart unless-stopped linuxserver/swag
docker start swag
. You can verify this is started by running docker ps
, which will produce an output like this:CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
09346434b8ea linuxserver/swag "/init" 2 minutes ago Up 5 seconds 0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp swag
/mnt/swag
). You'll then need to navigate to nginx/proxy-confs
within that directory. If you list the contents of that directory, you'll see a lot of files.jellyfin.subdomain.conf.sample
(if using a subdomain) or jellyfin.subfolder.conf.sample
(if using a subfolder). You'll want to copy the file needed, removing the .sample (ex. cp jellyfin.subdomain.conf.sample jellyfin.subdomain.conf
). Open the file in your text editor of choice.jellyfin.subdomain.conf
, although jellyfin.subfolder.conf
looks very similar):server {
listen 443 ssl;
listen [::]:443 ssl;
server_name jellyfin.*;
include /config/nginx/ssl.conf;
client_max_body_size 0;
location / {
include /config/nginx/proxy.conf;
resolver 127.0.0.11 valid=30s;
set $upstream_app jellyfin;
set $upstream_port 8096;
set $upstream_proto http;
proxy_pass $upstream_proto://$upstream_app:$upstream_port;
proxy_set_header Range $http_range;
proxy_set_header If-Range $http_if_range;
}
location ~ (/jellyfin)?/socket/ {
include /config/nginx/proxy.conf;
resolver 127.0.0.11 valid=30s;
set $upstream_app jellyfin;
set $upstream_port 8096;
set $upstream_proto http;
proxy_pass $upstream_proto://$upstream_app:$upstream_port;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $http_connection;
}
}
set $upstream_app jellyfin
. Now, assuming Jellyfin and Let's Encrypt are on the same network within Docker, it should see it and start handling reverse proxy without much issue. If it doesn't however, you'll just need to change jellyfin
in that line to whatever the IP of your Jellyfin server is. We'll also look at the line location ~ (/jellyfin)?/socket
and add a slash after socket, so the line should look like this location ~ (/jellyfin)?/socket/
.docker restart swag
, and then you can follow the logs with docker logs -f swag
. Assuming everything works, you should see Server Ready
at the very end of the logs. This tells you Lets Encrypt is running without issue.