Secure internal applications with a Let’s Encrypt certificate

In this article we will describe how you can secure an internal site with an automatically renewed Let’s Encrypt certificate. We will use Jellyfin Media Server as an example, but with a little modification this approach can be used for any internally running web application.


When you install Jellyfin, by default it will use HTTP. This means that data in transit is not encrypted. This might not really be an issue if you only use Jellyfin inside your network.

If you want to make Jellyfin accessible via the internet you can put Jellyfin behind a reverse proxy and let that arrange a SSL certificate for you. For this to work you normally have to open port 80 and 443 on your firewall, so that the reverse proxy can request the certificate.

But what if you do not want to expose Jellyfin to the internet and you do not want to open port 80 and 443, but you still want to secure Jellyfin with a SSL certificate?

We will show you how you can secure your Jellyfin Media Server with a SSL certificate. We will request this certificate through Let’s Encrypt, with certbot and we will use the DNS challenge method.

For this particular guide we make the following assumptions:

  • You have already installed Jellyfin and are using HTTP
  • You are running Jellyfin on Ubuntu Server
  • You are using Cloudflare to manage your domains DNS

Step 1: Install the tools

The first thing we have to do is to install the necessary tools on our Linux server to be able to request a Let’s Encrypt certificate. To do this, login to your Jellyfin server via SSH and install certbot. On Ubuntu you do this with the command below:

sudo apt install certbot python3-certbot-dns-cloudflare

Step 2: Get a Cloudflare API token

We are going to use a DNS challenge to get a certificate from Let’s Encrypt and we are going to use certbot to do this.

The way this works is when you request a certificate with certbot we will use the Cloudflare DNS plugin. This plugin will use the Cloudflare API to add a txt record, with a particular code, to your Cloudflare DNS.

Next Let’s Encrypt will query that txt record and validate the value. This is the DNS challenge. This will guarantee Let’s Encrypt that you have control over your domain and will allow them to issue you a certificate for the chosen domain.

For this to work we must create an API token and create a configuration file on our server first.


Step 2a: Create an API token

Go to cloudflare.com, login to your dashboard and choose your domain. On the bottom right you will find the Get your API token link.

In the next screen you can create your API token.

Click on the Create Token button and choose the Edit zone DNS template. Give it a proper name and select the zone you want to give this token access to under Zone Resources.

Click on Continue to summary and in the next screen click on Create Token. You will now be given a token which you should copy so that we can use it later.


Step 2b: Create a configuration file

Now back on your Jellyfin server we have to create a configuration file for certbot, so that it knows how to access the Cloudflare API.

nano ~/cloudflare.ini

Paste in the following content:

# Cloudflare API token used by Certbot
dns_cloudflare_api_token = IP8u9LEzu4EeFgP1O9Z6j9E4-5iKivaiNXkP96io

Then change the permissions of the file

chmod 600 cloudflare.ini

Step 3: Request the certificate

Now that we have our configuration file, which contains our API key, we will use certbot to request the certificate. Execute the command below to do this.

sudo certbot certonly \
--dns-cloudflare \
--dns-cloudflare-credentials ~/cloudflare.ini \
-d jellyfin.internal.example.com

What we do here, is we instruct certbot to request a certificate for the domain jellyfin.internal.example.com and use Cloudflare DNS challenge, while also telling it which credentials to use (the API key).

Once this command completes you will find the certificates that were issued in the following directory:
/etc/letsencrypt/live/jellyfin.internal.example.com/

The files you will find here are:

  • privkey.pem
  • fullchain.pem
  • chain.pem
  • cert.pem

While this is great, actually none of these are directly usable for Jellyfin, as Jellyfin requires a PKCS#12 certificate. We will have to create that.


Step 4: Create the hook script

Whenever you renew a certificate, you can run some extra scripts before or after. We are going to use this to run a script after renewal. This script will:

  • Generate PKCS#12 file for Jellyfin
  • Change PKCS#12 file permissions
  • Restart Jellyfin

This script must be placed in:
/etc/letsencrypt/renewal-hooks/post

To do this, do the following. Create the script jellyfin.sh:

cd /etc/letsencrypt/renewal-hooks/post
sudo nano jellyfin.sh

Paste in the following code:

#!/bin/bash

# Generate PKCS12 file for Jellyfin
openssl pkcs12 \
-export \
-inkey /etc/letsencrypt/live/jellyfin.internal.example.com/privkey.pem \
-in /etc/letsencrypt/live/jellyfin.internal.example.com/cert.pem -name jellyfin \
-out /etc/letsencrypt/live/jellyfin.internal.example.com/jellyfin.pfx \
-password pass:

# Change PKCS12 file permissions
chmod 755 /etc/letsencrypt/live/jellyfin.internal.example.com/jellyfin.pfx

# Restart Jellyfin
systemctl restart jellyfin.service

The first command uses openssl to generate the PKCS#12 certificate. It basically combines the signed certificate and the private key in one file. The output file is jellyfin.pfx and there is no password set on this certificate.

Next it changes the jellyfin.pfx file permissions and restarts Jellyfin.

Finally set the permissions of this script:

sudo chmod 700 jellyfin.sh

The jellyfin.sh script will now be executed every time you renew the certificate.


Step 5: Renew the certificate

Now let’s test if this works. Let’s renew the certificate and see what happens. Issue the following command to renew the certificate:

sudo certbot renew --force-renew

This will now request a renewal of the certificate through Let’s Encrypt and run the post renewal hook automatically.

Afterwards you should see in the /etc/letsencrypt/live/jellyfin.internal.example.com/ directory a file named jellyfin.pfx being generated.
This is the certificate we can use in Jellyfin to enable HTTPS.


Step 6: Configure the certificate in Jellyfin

Login to your Jellyfin instance and go to the Network section in your Dashboard.

Here you should enable HTTPS and in the HTTPS Setting section point Jellyfin to the certificate to use.

Now restart Jellyfin (or the Ubuntu server) and if you connect to https://jellyfin.internal.example.com:8920 you will see that you have properly secured Jellyfin with a Let’s Encrypt certificate.


Step 7: Certificate renewal

You have to renew your certificate before it expires. These Let’s Encrypt certificates expire relatively fast, namely after 90 days.

Luckily certbot does this automatically for you. When you requested the certificate certbot also created a systemd timer to monitor your certificate’s validity. This timer will renew the certificate when needed.


Posted

in

by