After installing Cockpit you will likely end up with certificates that are self-signed and therefore untrusted by default.

There is a better way; Let’s Encrypt.

NOTE: Through out the article the fully-qualified domain name of the Cockpit server is treated as a variable (e.g., $FQDN). Make sure to either define a similar variable with an appropriate value or replace it when copying the commands.

Install certbot

First thing is first, install certbot1. On Fedora 33, the certbot tool is provided via the system package manager (e.g., python3-certbot). Most Linux distributions have a simple way to install certbot through the system package manager; check yours.

The other thing you will need is a way for the certificate authority (CA), in this case Let’s Encrypt, to verify ownership of the domain for the certificate. There are a few different challenge types that can be used. I prefer DNS and use Amazon Route 53 so I will assume that in the rest of this.

$ dnf install python3-certbot python3-certbot-dns-route53

Register ACME account

In order, for the ACME server to issue certificates an account must be created. This is simple and only requires an e-mail address and agreeing to the terms of service. If you would like to continue further you must agree to the terms.

$ certbot register --email "use-a-real-address@spam.lovelett.me"

Request Certificate

Now the final part is requesting and downloading the X.509 certificates. Again, I prefer the DNS challenge specifically through Amazon Route 53 so I use the --dns-route53 flag. You might prefer a different challenge. Please read the manual for other options.

$ certbot certonly --dns-route53 --domains $FQDN

After this completes you should have certificates in /etc/letsencrypt/live/$FQDN. Additionally, you can verify the certificates using certbot certificates.

$ certbot certificates
Saving debug log to /var/log/letsencrypt/letsencrypt.log

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Found the following certs:
  Certificate Name: $FQDN
    Serial Number: 3dd2fe7e49a5686d55f9c7c8ef790465cdf
    Key Type: RSA
    Domains: $FQDN
    Expiry Date: 2021-06-09 01:25:21+00:00 (VALID: 89 days)
    Certificate Path: /etc/letsencrypt/live/$FQDN/fullchain.pem
    Private Key Path: /etc/letsencrypt/live/$FQDN/privkey.pem
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Notice that the certs expire? Setting up systemd units and timers certbot can be configured to automatically renew these certs.

Certbot Systemd Unit and Timers

The tool provides a certbot renew function that handles renewal of certificates. The first thing to do is to create the systemd service.

/etc/systemd/system/certbot.service

[Unit]
Description=Let's Encrypt renewal via Certbot
Documentation=https://letsencrypt.readthedocs.io/en/latest/

[Service]
Type=oneshot
ExecStart=/usr/bin/certbot renew --quiet --agree-tos

Then the timer.

/etc/systemd/system/certbot.timer

[Unit]
Description=Twice daily renewal of Let's Encrypt's certificates

[Timer]
OnCalendar=0/12:00:00
RandomizedDelaySec=1h
Persistent=true

[Install]
WantedBy=timers.target

Then it should be as simple as reloading systemd (e.g., systemctl daemon-reload) and then starting the timer (e.g., systemctl enable --now certbot.timer).

To check the status you can use

$ systemctl list-timers
NEXT                        LEFT          LAST                        PASSED    UNIT                         ACTIVATES
Thu 2021-03-11 00:10:56 EST 1h 20min left n/a                         n/a       certbot.timer                certbot.service

1 timers listed.
Pass --all to see loaded but inactive timers, too.

If your certbot plugin requires environment variables, like with Route 53 you must provide AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY, a great way to provide these is through providing an override. This can be done using systemctl edit certbot.service.

[Service]
Environment="AWS_ACCESS_KEY_ID=<key>"
Environment="AWS_SECRET_ACCESS_KEY=<secret>"

Configure Cockpit

Cockpit loads certificates from the /etc/cockpit/ws-certs.d directory. It will use the last file in the directory with a .cert or .crt extension in alphabetical order. The private key can be contained in a separate file with the same name as the certificate, but with a .key suffix instead. The key must not be encrypted.

This can be achieved by copying the Let’s Encrypt key and certificate to the correct location for Cockpit.

$ cp /etc/letsencrypt/live/$FQDN/fullchain.pem /etc/cockpit/ws-certs.d/$FQDN.crt
$ cp /etc/letsencrypt/live/$FQDN/privkey.pem /etc/cockpit/ws-certs.d/$FQDN.key
# Note: The user and group may be different
$ chown cockpit-ws:cockpit-ws /etc/cockpit/ws-certs.d/$FQDN.crt /etc/cockpit/ws-certs.d/$FQDN.key

To check that the certificates Cockpit will use is now correct run the following command:

$ remotectl certificate
certificate: /etc/cockpit/ws-certs.d/$FQDN.crt

Restarting the Cockpit server (systemctl restart cockpit) should get the updated certificate in place. To make sure that Cockpit always renews after a certificate renewal make a renewal hook.

/etc/letsencrypt/renewal-hooks/post/001-restart-cockpit.sh

#!/usr/bin/env bash

echo "SSL certificates renewed"

cp /etc/letsencrypt/live/$FQDN/fullchain.pem /etc/cockpit/ws-certs.d/$FQDN.crt
cp /etc/letsencrypt/live/$FQDN/privkey.pem /etc/cockpit/ws-certs.d/$FQDN.key
chown cockpit-ws:cockpit-ws /etc/cockpit/ws-certs.d/$FQDN.crt /etc/cockpit/ws-certs.d/$FQDN.key

echo "Restarting Cockpit"
systemctl restart cockpit

Backup


  1. certbot is a tool that speaks the Automated Certificate Management Environment (ACME) protocol that Let’s Encrypt uses to issue its certificates. It is worth noting that certbot does not have to speak to Let’s Encrypt. Realistically any certificate authority (CA) that speaks the ACME protocol will do. For example, Fraser Tweedale got it working with Dogtag’s PKI ACME responder. ↩︎