NGINX Unit
v. 1.28.0

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.

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"
            }
        },
    
        "routes": {
            "acme": [
                {
                    "match": {
                        "uri": "/.well-known/acme-challenge/*"
                    },
    
                    "action": {
                        "share": "/var/www/www.example.com/"
                    }
                }
            ]
        }
    }
    

    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
    

    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
    

    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/
    └── live/
        └── www.example.com
            ├── cert.pem
            ├── chain.pem
            ├── fullchain.pem
            └── privkey.pem
    

    Note

    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
    
    # curl -X PUT --data-binary @bundle1.pem  \
           --unix-socket /path/to/control.unit.sock  \
           http://localhost/certificates/certbot1
    
           {
               "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"}}'  \
          --unix-socket /path/to/control.unit.sock  \
          'http://localhost/config/listeners/*:443'
    
  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
    
    # curl -X PUT --data-binary @bundle2.pem  \
           --unix-socket /path/to/control.unit.sock  \
           http://localhost/certificates/certbot2
    
           {
               "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  \
           http://localhost/certificates
    
  2. Update the listener, switching it to the renewed certificate bundle:

    # curl -X PUT --data-binary 'certbot2'  \
          --unix-socket /path/to/control.unit.sock  \
          'http://localhost/config/listeners/*:443/tls/certificate'
    

    Note

    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  \
          'http://localhost/certificates/certbot1'
    
          {
              "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
    
    # cat /etc/letsencrypt/live/www.example.com/fullchain.pem  \
          /etc/letsencrypt/live/www.example.com/privkey.pem > www.example.com.pem
    
    
    # curl -X PUT --data-binary @cdn.example.com.pem  \
           --unix-socket /path/to/control.unit.sock  \
           http://localhost/certificates/cdn.example.com
    
           {
               "success": "Certificate chain uploaded."
           }
    
    # curl -X PUT --data-binary @www.example.com.pem  \
           --unix-socket /path/to/control.unit.sock  \
           http://localhost/certificates/www.example.com
    
           {
               "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"]}'  \
          --unix-socket /path/to/control.unit.sock  \
          'http://localhost/config/listeners/*:443/tls'
    

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

Note

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.