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.
Configuring
In the plaintext OpenHTTPd guide, we used
/etc/examples/httpd.conf
as a template for
httpd.conf(5), with two sections
commented out:
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.fullchain.pem" # 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 these two sections because we did not yet request TLS certs. Now that we have certs from acme-client, we will uncomment the second block.
TLS Block Explained
Below, we have uncommented the block (and made one significant change). We will provide 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 } }
First note that in line 4 above, we changed the certificate from
/etc/ssl/example.com.fullchain.pem
to /etc/ssl/example.com.crt
Here, we deviate from the example
httpd.conf(5) because we later plan to
use relayd to provide SSL acceleration, and
relayd hard codes the paths it searches for the public
and private keypair. relayd(8) will only
search for public certificates that end in the .crt
suffix; it will ignore
certificates that end with the suffix .fullchain.pem
.
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 certs to use.
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.
Note: You can (optionally) leave the 302 forwarding block commented out:
# location * { # block return 302 "https://$HTTP_HOST$REQUEST_URI" # }
We recommend leaving this block commented out because some (old) web browsers do not support modern TLS, and so allowing plaintext access can improve accessibility. Plaintext access may be important in some restrictive countries that prohibit modern TLS.
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
Use ps or pgrep to ensure that all httpd(8) processes have been stopped:
$ pgrep httpd
If all httpd processes have been stopped, you should see no output. If you see a number representing a process ID, see the guides on killing processes.
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 C = US, O = Internet Security Research Group, CN = ISRG Root X1 verify return:1 depth=1 C = US, O = Let's Encrypt, CN = R11 verify return:1 depth=0 CN = example.com verify return:1 --- Certificate chain 0 s:/CN=example.com i:/C=US/O=Let's Encrypt/CN=R11 1 s:/C=US/O=Let's Encrypt/CN=R11 i:/C=US/O=Internet Security Research Group/CN=ISRG Root X1 --- Server certificate -----BEGIN CERTIFICATE----- MIIF+jCCBOKgAwIBAgISBBiSmYI1JcgnGriQsYnjgYNaMA0GCSqGSIb3DQEBCwUA MDMxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQwwCgYDVQQD EwNSMTEwHhcNMjQxMTE0MDAzNjU3WhcNMjUwMjEyMDAzNjU2WjAfMR0wGwYDVQQD ExRqcm11Lmhvc3QuaXJjbm93Lm9yZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC AgoCggIBANuLaDDkipvSVq4lPmSymFhbnqt7Exv3LRmzq6YvqzWpLOd1wkHNXFHg yxCE6AbVdz3jqZT00sVO1uF/A3YdN63qlziWJFP1GaCZzcyuJ7a2NAVX/igggxO3 guwzlfFh844AoudJ3+KPBCGfCmI8qWftjOTIz4/huCr3CRsPwuABySWKGh/p9n+3 wJE5EU425hkiTGGDNhF65aU8B/cT3clhdkFKwcNGEX4vkrQwlZeF43Mj9cQf3G3v uAOdP0DEGqhxyYQUrsGP/ml9S99VnQ91hxta1J4EYwTqCnG4UwyZ/unFJ3vRpajQ /8LKkVPBQxKaREJNafB0cv29sEqE2RTBWzot8RT6mSFN59b07O7m4pxqHs+OenkW ltH3lM9pwrFBc0RLipAXkkgauVSohBH7SbVuMDIwCMYFdOHCBRqgW6eDTk+hhklh nXWR0JJ2lRF1IUQQjduJWadEUDK9O/iUfLfnZr1a5ZfjXs4dlFqVU8NUQWQd3G5J 9d4iCX7VkEigXlJrxTgbohFLkPzeDiSPqdwKqx1GMEWLxrW65a71UR81AJEYTJJE ixOwGEb1kXtGEqKhM4CYywBLKiDNOEoMPsRg3UsOfHS1eaSDF6io42brmhKILAJL SP5CTPZw5LYKaqc+aO13keucLBTne5+aWhaQBD0ihqsssYPlxFehAgMBAAGjggIa MIICFjAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUF BwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFAVeGed5J7eod57c69KSUDiVDq7m MB8GA1UdIwQYMBaAFMXPRqTq9MPAemyVxC2wXpIvJuO5MFcGCCsGAQUFBwEBBEsw STAiBggrBgEFBQcwAYYWaHR0cDovL3IxMS5vLmxlbmNyLm9yZzAjBggrBgEFBQcw AoYXaHR0cDovL3IxMS5pLmxlbmNyLm9yZy8wHwYDVR0RBBgwFoIUanJtdS5ob3N0 LmlyY25vdy5vcmcwEwYDVR0gBAwwCjAIBgZngQwBAgEwggEGBgorBgEEAdZ5AgQC BIH3BIH0APIAdwDPEVbu1S58r/OHW9lpLpvpGnFnSrAX7KwB0lt3zsw7CAAAAZMo T6OZAAAEAwBIMEYCIQD+t4oiZ3lkJeY+nH1glYZjlktnSc31rKjJlBbJwnPTfwIh ALwVTA0TNEa2jo5zmOq7nypo7awprI48XnDofYsb7GK+AHcAE0rfGrWYQgl4DG/v THqRpBa3I0nOWFdq367ap8Kr4CIAAAGTKE+kEwAABAMASDBGAiEA5nosfBa3GTMC Rw9xjef4RVpwdvaaRsC8xDZy95CW86ECIQCLSXo2BqI9coah2trzV3gxq0LnEn9r XcciSxO0ZH4mCzANBgkqhkiG9w0BAQsFAAOCAQEApjLhmAFD1bEgI5lxzIcGQrdM 3CSgDn7OZEqQS6pbmTGdjk3aiWAUNsNlwBdatdWra171lytEd2wufDf/iN7RWkcK 6BK3RZeTsKK8KNdKiV7oXL9Kd/1NpYSHizVN1obqF3Knh1JM+Kes6YXTxod7L1Av ozkhle3d61jrUhUz4VEp053pNxi8ylDRd6jeDnIAQbAJlGJapD1P3Sfy0VL+Kprs ZoEucBa3ZaSh+JNNS0fxSnl/qKfWlwOSsiMNL8yj7sy6hcVEgWqhMkviGGYpNikY harUihdi26bReT1MXM9nFsYZa20+B1BUGk7Y/0TQ7zo1JtjhSXVIP4pB1zpuwg== -----END CERTIFICATE----- subject=/CN=example.com issuer=/C=US/O=Let's Encrypt/CN=R11 --- No client certificate CA names sent Server Temp Key: ECDH, X25519, 253 bits --- SSL handshake has read 3645 bytes and written 386 bytes --- New, TLSv1/SSLv3, Cipher is TLS_AES_256_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 : TLS_AES_256_GCM_SHA384 Session-ID: Session-ID-ctx: Master-Key: Start Time: 1731552214 Timeout : 7200 (sec) Verify return code: 0 (ok) ---
At this point, you can make normal HTTP GET requests like with netcat or telnet.
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 should automate the renewal of TLS certs. Otherwise, once a cert expires, your users will be confronted with invalid certificate errors.
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 reloads httpd(8) to use it.
Troubleshooting
Configuration Errors
If you were unable to establish the connection above, the first test is to see if httpd(8) has any critical errors.
First, stop all httpd processes:
$ doas rcctl stop httpd
Next, confirm that httpd is no longer running:
$ ps -ax | grep httpd
You may need to kill any httpd processes that aren't shut down with rcctl.
Finally, run httpd(8) in debug mode:
$ doas httpd -d
Observe if there are any errors. If so, fix those errors before proceeding with troubleshooting.
Permissions Error
$ ls -l /var/www/htdocs/index.html -rw-r--r-- 1 root daemon 14 Nov 12 00:15 /var/www/htdocs/index.html
If the file index.html is not readable by httpd, the webpage cannot load. See chmod(1) and chown for more information about permissions on BSD.
Firewall
If you changed your firewall rules?, double check to ensure your firewall is not blocking port 80. By default, packet filter? allows web traffic, so the firewall will not block web traffic unless you have edited the default firewall ruleset.
See Also:
- Httpd and Relayd Mastery contains many helpful examples.
- https://learnbchs.org/LearnBCHS
- acme-client
- OpenHTTPd (with TLS)