TLS with Certbot

To set up SSL/TLS access in Unit, you need certificate bundles. Although you can use self-signed certificates, it’s advisable to obtain certificates for your website from a certificate authority (CA). For this purpose, you may employ EFF’s Certbot that issues free certificates signed by Let’s Encrypt, a non-profit CA.

The commands in this document starting with a hash (#) must be run as root or with superuser privileges.

Generating certificates

  1. Install Unit on your website’s server.

  2. Install Certbot on the same server, choosing None of the above in the Software dropdown list and the server’s OS in the System dropdown list at EFF’s website.

  3. Run the certbot utility and follow its instructions to create the certificate bundle. You’ll be prompted to enter the domain name of the website and validate domain ownership; the latter can be done differently. Perhaps, the easiest approach is to use the webroot method by having Certbot store a certain file locally and then access it by your domain name. First, configure Unit with a temporary route at port 80:

    {
       "listeners": {
          "*:80": {
                "pass": "routes/acme",
                "comment_*:80": "Certbot attempts to reach the domain name at port 80"
          }
       },
       "routes": {
          "acme": [
                {
                   "match": {
                      "uri": "/.well-known/acme-challenge/*",
                      "comment_uri": "The URI that Certbot probes to download the file"
                   },
                   "action": {
                      "share": "/var/www/www.example.com$uri/",
                      "comment_share": "Arbitrary directory, preferably the one used for storing static files"
                   }
                }
          ]
       }
    }

    Make sure the share directory is accessible for Unit’s router process user account, usually unit:unit.

    Next, run certbot, supplying the share directory as the webroot path:

    # certbot certonly --webroot -w /var/www/www.example.com/ -d www.example.com # path where the file is stored and your domain name
    

    If you can’t employ the previous method for some reason, try using DNS records to validate your domain:

    # certbot certonly --manual --preferred-challenges dns -d www.example.com # your domain name
    

    Certbot will provide instructions on updating the DNS entries to prove domain ownership.

    Any such certbot command stores the resulting .pem files as follows:

    /etc/letsencrypt/       # Location can be configured, see Certbot help
    └── live/
       └── www.example.com  # Your website name
       ├── cert.pem         # Leaf website certificate
          ├── chain.pem     # Root CA certificate chain
          ├── fullchain.pem # Concatenation of the two PEMs above
          └── privkey.pem   # Your private key, must be kept secret
    Certbot offers other validation methods (authenticators) as well, but they’re omitted here for brevity.
  4. Create a certificate bundle fit for Unit and upload it to the certificates section of Unit’s control API:

    # cat /etc/letsencrypt/live/www.example.com/fullchain.pem  \
          /etc/letsencrypt/live/www.example.com/privkey.pem > bundle1.pem # Arbitrary certificate bundle's filename
    
    # curl -X PUT --data-binary @bundle1.pem \ # Certificate bundle's filename
          --unix-socket /path/to/control.unit.sock \ # Path to Unit's control socket in your installation
          http://localhost/certificates/certbot1  # Certificate bundle name in Unit's configuration
    
    {
       "success": "Certificate chain uploaded."
    }
    
  5. Create or update a listener to use the uploaded bundle in Unit:

    # curl -X PUT --data-binary \
          '{"pass": "applications/ssl_app", "tls": {"certificate": "certbot1"}}' \ # Certificate bundle name in Unit's configuration
          --unix-socket /path/to/control.unit.sock \ # Path to Unit's control socket in your installation
          'http://localhost/config/listeners/*:443'  # Listener's name in Unit's configuration
    
  6. Try accessing your website via HTTPS:

    $ curl https://www.example.com -v
    
          ...
          * TLSv1.3 (OUT), TLS handshake, Client hello (1):
          * TLSv1.3 (IN), TLS handshake, Server hello (2):
          * TLSv1.3 (IN), TLS Unknown, Certificate Status (22):
          * TLSv1.3 (IN), TLS handshake, Unknown (8):
          * TLSv1.3 (IN), TLS Unknown, Certificate Status (22):
          * TLSv1.3 (IN), TLS handshake, Certificate (11):
          * TLSv1.3 (IN), TLS Unknown, Certificate Status (22):
          * TLSv1.3 (IN), TLS handshake, CERT verify (15):
          * TLSv1.3 (IN), TLS Unknown, Certificate Status (22):
          * TLSv1.3 (IN), TLS handshake, Finished (20):
          * TLSv1.3 (OUT), TLS change cipher, Client hello (1):
          * TLSv1.3 (OUT), TLS Unknown, Certificate Status (22):
          * TLSv1.3 (OUT), TLS handshake, Finished (20):
          * SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
          * ALPN, server did not agree to a protocol
          * Server certificate:
          *  subject: CN=www.example.com
          *  start date: Sep 21 22:10:42 2020 GMT
          *  expire date: Dec 20 22:10:42 2020 GMT
          ...
    

Renewing certificates

Certbot enables renewing the certificates manually or automatically. For manual renewal and rollover:

  1. Repeat the preceding steps to renew the certificates and upload the new bundle under a different name:

    # certbot certonly --standalone
    
          What would you like to do?
          - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
          1: Keep the existing certificate for now
          2: Renew & replace the cert (may be subject to CA rate limits)
    
    # cat /etc/letsencrypt/live/www.example.com/fullchain.pem  \
          /etc/letsencrypt/live/www.example.com/privkey.pem > bundle2.pem # Arbitrary certificate bundle's filename
    
    # curl -X PUT --data-binary @bundle2.pem \ # Certificate bundle's filename
          --unix-socket /path/to/control.unit.sock \ # Path to Unit's control socket in your installation
          http://localhost/certificates/certbot2  # Certificate bundle name in Unit's configuration
    
    {
       "success": "Certificate chain uploaded."
    }
    

    Now you have two certificate bundles uploaded; Unit knows them as certbot1 and certbot2. Optionally, query the certificates section to review common details such as expiry dates, subjects, or issuers:

    # curl --unix-socket /path/to/control.unit.sock  \ # Path to Unit's control socket in your installation
           http://localhost/certificates
    
  2. Update the listener, switching it to the renewed certificate bundle:

    # curl -X PUT --data-binary 'certbot2' \ # New certificate bundle name in Unit's configuration
          --unix-socket /path/to/control.unit.sock \ # Path to Unit's control socket in your installation
          'http://localhost/config/listeners/*:443/tls/certificate'  # Listener's name in Unit's configuration
    
    There’s no need to shut Unit down; your server can stay online during the rollover.
  3. Delete the expired bundle:

    # curl -X DELETE --unix-socket /path/to/control.unit.sock \ # Path to Unit's control socket in your installation
          'http://localhost/certificates/certbot1'  # Old certificate bundle name in Unit's configuration
    
    {
       "success": "Certificate deleted."
    }
    
  4. You can also make use of Unit’s SNI support by configuring several certificate bundles for a listener.

    Suppose you’ve successfully used Certbot to obtain Let’s Encrypt certificates for two domains, www.example.com and cdn.example.com. First, upload them to Unit using the same steps as earlier:

    # cat /etc/letsencrypt/live/cdn.example.com/fullchain.pem  \
          /etc/letsencrypt/live/cdn.example.com/privkey.pem > cdn.example.com.pem # Arbitrary certificate bundle's filename
    
    # cat /etc/letsencrypt/live/www.example.com/fullchain.pem  \
          /etc/letsencrypt/live/www.example.com/privkey.pem > www.example.com.pem # Arbitrary certificate bundle's filename
    
    # curl -X PUT --data-binary @cdn.example.com.pem \ # Certificate bundle's filename
          --unix-socket /path/to/control.unit.sock \ # Path to Unit's control socket in your installation
          http://localhost/certificates/cdn.example.com  # Certificate bundle name in Unit's configuration
    
    {
       "success": "Certificate chain uploaded."
    }
    
    # curl -X PUT --data-binary @www.example.com.pem \ # Certificate bundle's filename
          --unix-socket /path/to/control.unit.sock \ # Path to Unit's control socket in your installation
          http://localhost/certificates/www.example.com  # Certificate bundle name in Unit's configuration
    
    {
       "success": "Certificate chain uploaded."
    }
    

    Next, configure the listener, supplying both bundles as an array value for the tls/certificate option:

    # curl -X PUT --data-binary '{"certificate": ["cdn.example.com", "www.example.com"]}' \ # Certificate bundle names in Unit's configuration
          --unix-socket /path/to/control.unit.sock \ # Path to Unit's control socket in your installation
          'http://localhost/config/listeners/*:443/tls'  # Listener's name in Unit's configuration
    

    Unit does the rest of the job, automatically figuring out which bundle to produce for each incoming connection to both domain names.

Currently, Certbot doesn’t have installer plugins that enable automatic certificate rollover in Unit. However, you can set up Certbot’s hooks using the commands listed here to the same effect.

Last modified March 19, 2025