TLS for OpenHTTPd

This guide shows you how to enable TLS? for OpenHTTPd. It assumes you have already set up plaintext OpenHTTPd listening on port 80, and you have successfully requested TLS certs using acme-client.

Docs and references

Consult httpd, httpd.conf, acme-client, and acme-client.conf man pages. Httpd and Relayd Mastery also contains many helpful examples.

Configuring

In the previous guide, we used /etc/examples/httpd.conf as a template for /etc/httpd.conf:

server "example.com" {
       listen on * port 80
       location "/.well-known/acme-challenge/*" {
               root "/acme"
               request strip 2
       }
       location * {
                 block return 302 "https://$HTTP_HOST$REQUEST_URI"
       }
 }

server "example.com" {
        listen on * tls port 443
        tls {
                certificate "/etc/ssl/example.com.crt"
                key "/etc/ssl/private/example.com.key"
        }
        location "/pub/*" {
                directory auto index
        }
        location "/.well-known/acme-challenge/*" {
                root "/acme"
                request strip 2
        }
}

NOTE: You must replace example.com with your own domain

We commented out the second block in the basic OpenHTTPd guide because we did not yet request TLS certs yet. Now that we have certs from acme-client, we uncomment the second block.

TLS Block Explained

Here is a line-by-line description of the TLS block:

server "example.com" {
        listen on * tls port 443
        tls {
                certificate "/etc/ssl/example.com.crt"
                key "/etc/ssl/private/example.com.key"
        }
        location "/pub/*" {
                directory auto index
        }
        location "/.well-known/acme-challenge/*" {
                root "/acme"
                request strip 2
        }
}

Lines 2-6 tells the web server to listen on all IPs on port 443. As a result, we need a tls block to specify which SSL certs to use. Again, it is necessary to replace example.com with your actual hostname.

Lines 7-9 say that, for any request beginning with https://example.com/pub/, the web server should automatically show a directory listing. Normally this is not a good idea for security reasons, but for a public folder, it should be fine.

In a normal production server, if OpenHTTPd is already running, reloading is best to avoid downtime:

$ doas rcctl reload httpd

For your first test however, you will want to stop OpenHTTPd?:

$ doas rcctl stop httpd

Then, check that your configuration is valid:

$ doas httpd -n

Once you are certain it has been configured properly, you can start the server:

$ doas rcctl start httpd

Testing

To test if your web server has a working SSL cert, use openssl:

$ openssl s_client -connect example.com:443

NOTE: You must replace example.com with your actual hostname.

You should see the correct SSL subject and issuer:

$ openssl s_client -connect example.org:443
CONNECTED(00000003)
depth=2 O = Digital Signature Trust Co., CN = DST Root CA X3
verify return:1
depth=1 C = US, O = Let's Encrypt, CN = R3
verify return:1
depth=0 CN = example.com
verify return:1
depth=0 CN = example.com
verify return:1
write W BLOCK
---
Certificate chain
 0 s:/CN=example.com
   i:/C=US/O=Let's Encrypt/CN=R3
 1 s:/C=US/O=Let's Encrypt/CN=R3
   i:/O=Digital Signature Trust Co./CN=DST Root CA X3
---
Server certificate
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
subject=/CN=example.com
issuer=/C=US/O=Let's Encrypt/CN=R3
---
No client certificate CA names sent
Server Temp Key: ECDH, X25519, 253 bits
---
SSL handshake has read 3730 bytes and written 367 bytes
---
New, TLSv1/SSLv3, Cipher is AEAD-AES256-GCM-SHA384
Server public key is 4096 bit
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
    Protocol  : TLSv1.3
    Cipher    : AEAD-AES256-GCM-SHA384
    Session-ID:
    Session-ID-ctx:
    Master-Key:
    Start Time: 1614233943
    Timeout   : 7200 (sec)
    Verify return code: 0 (ok)
---

You can also visit the website using your web browser. Open your web browser to https://example.com. If you see an error such as 403 Forbidden, it may mean you have not set up a website.

Look for the SSL padlock in the address bar (which indicates your site is secure), then view more information about the certificate:

Automation

Let's Encrypt TLS certs expire after 90 days. As a result, you are highly encouraged to automate the renewal of TLS certs. Otherwise, once a cert expires, your users may no longer be able to visit your site.

We can automate the request process using crontab.

$ doas crontab -e

Add this line at the bottom:

~       ~       *       *       *       acme-client example.com >> /var/log/acme-client.log 2>&1 && sleep 300 && rcctl reload httpd

This cronjob will check the certificate once each day at a random time to see if it needs to be renewed. If it does, it will renew the cert, wait 300 seconds, then reload openhttpd to use it.

Troubleshooting

If you were unable to establish the connection above, it may be because your firewall is blocking port 443.

You can ensure pf allows incoming http connections by putting this line into /etc/pf.conf:

pass in quick proto tcp to port {http https}

Then, reload the pf rulesets:

$ doas pfctl -f /etc/pf.conf