OpenBSD httpd and local https
This tutorial configures a local OpenBSD web server for:
Hostname:
minifishpc01.ronverbs.devDocument root:
/var/www/htdocs/chronicle-my-brainAccess: the OpenBSD host itself
Web server: OpenBSD
httpdCertificate authority: a private, locally trusted CA
The result will be available at:
https://minifishpc01.ronverbs.dev/
Because this is a local-only hostname, Let’s Encrypt’s HTTP-01 validation is not appropriate. A private CA lets the browser verify the server certificate without making the development site publicly accessible.
The .dev top-level domain is treated as HTTPS-only by modern browsers. A certificate warning may therefore be difficult or impossible to bypass. The correct solution is to import the private CA certificate as a trusted authority.
1. Configure local hostname resolution
Since the website will be opened only on this OpenBSD machine, /etc/hosts is sufficient. Add:
127.0.0.1 minifishpc01.ronverbs.dev
Edit the file:
doas vi /etc/hosts
Test the name:
ping -c 1 minifishpc01.ronverbs.dev
It should resolve to 127.0.0.1.
3. Create the server certificate
Modern browsers verify the Subject Alternative Name rather than relying solely on the certificate’s Common Name. Create /etc/ssl/minifishpc01.ronverbs.dev.cnf:
[ v3_server ]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
basicConstraints = critical, CA:false
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names
[ alt_names ]
DNS.1 = minifishpc01.ronverbs.dev
Generate the server private key:
doas openssl genrsa \
-out /etc/ssl/private/minifishpc01.ronverbs.dev.key 2048
Protect it:
doas chmod 600 \
/etc/ssl/private/minifishpc01.ronverbs.dev.key
Create a certificate-signing request:
doas openssl req -new -sha256 \
-key /etc/ssl/private/minifishpc01.ronverbs.dev.key \
-out /tmp/minifishpc01.ronverbs.dev.csr \
-subj "/CN=minifishpc01.ronverbs.dev"
Sign the request with the private CA:
doas openssl x509 -req -sha256 -days 365 \
-in /tmp/minifishpc01.ronverbs.dev.csr \
-CA /etc/ssl/ronverbs-local-ca.crt \
-CAkey /etc/ssl/private/ronverbs-local-ca.key \
-CAcreateserial \
-out /etc/ssl/minifishpc01.ronverbs.dev.crt \
-extfile /etc/ssl/minifishpc01.ronverbs.dev.cnf \
-extensions v3_server
Remove the temporary request:
rm /tmp/minifishpc01.ronverbs.dev.csr
Inspect the certificate:
openssl x509 \
-in /etc/ssl/minifishpc01.ronverbs.dev.crt \
-noout -subject -issuer -dates -ext subjectAltName
Confirm that the CA signature is valid:
openssl verify \
-CAfile /etc/ssl/ronverbs-local-ca.crt \
/etc/ssl/minifishpc01.ronverbs.dev.crt
The result should be:
/etc/ssl/minifishpc01.ronverbs.dev.crt: OK
4. Configure OpenBSD httpd
Add these server blocks to /etc/httpd.conf:
server "minifishpc01.ronverbs.dev" {
listen on 127.0.0.1 port 80
block return 301 \
"https://minifishpc01.ronverbs.dev$REQUEST_URI"
}
server "minifishpc01.ronverbs.dev" {
listen on 127.0.0.1 tls port 443
tls {
certificate "/etc/ssl/minifishpc01.ronverbs.dev.crt"
key "/etc/ssl/private/minifishpc01.ronverbs.dev.key"
}
root "/htdocs/chronicle-my-brain"
directory index "index.html"
}
OpenBSD httpd is chrooted under /var/www. Consequently, this configuration path:
/htdocs/chronicle-my-brain
refers to this actual filesystem directory:
/var/www/htdocs/chronicle-my-brain
The server’s private key stays outside the chroot under /etc/ssl/private.
5. Validate and start httpd
Check the configuration before restarting anything:
doas httpd -n
The expected result is:
configuration OK
Enable and restart the service:
doas rcctl enable httpd
doas rcctl restart httpd
Confirm that it is running:
doas rcctl check httpd
If troubleshooting is required, confirm that ports 80 and 443 are listening:
netstat -an -f inet | grep LISTEN
6. Import the CA into Firefox
Copy the public CA certificate into Downloads so the browser’s restricted file picker can see it:
cp /etc/ssl/ronverbs-local-ca.crt "$HOME/Downloads/"
In Firefox:
Open Settings.
Select Privacy & Security.
Find Certificates.
Select View Certificates.
Open Authorities.
Select Import.
Choose
ronverbs-local-ca.crt.Enable trust for identifying websites.
Restart Firefox.
Import only the .crt file. Never import or copy the CA’s private .key file.
7. Import the CA into Chrome or Chromium
The CA certificate should already be in Downloads:
$HOME/Downloads/ronverbs-local-ca.crt
In Chrome or Chromium:
Open
chrome://certificate-manager.Select Authorities.
Select Import.
Choose
ronverbs-local-ca.crt.Enable Trust this certificate for identifying websites.
Completely close and restart the browser.
NSS command-line alternative
If the browser does not provide an Import button, install the NSS utilities:
doas pkg_add nss
Close Chrome or Chromium completely. Create its NSS database if one does not already exist:
mkdir -p "$HOME/.pki/nssdb"
certutil -N --empty-password -d "sql:$HOME/.pki/nssdb"
If the database already exists, do not recreate it. Import the CA:
certutil -A \
-d "sql:$HOME/.pki/nssdb" \
-n "Ronverbs Local Development CA" \
-t "C,," \
-i "$HOME/Downloads/ronverbs-local-ca.crt"
Confirm the import:
certutil -L -d "sql:$HOME/.pki/nssdb"
The certificate list should contain:
Ronverbs Local Development CA
Restart the browser.
8. Test the finished site
Test the TLS handshake independently of the browser:
openssl s_client \
-connect minifishpc01.ronverbs.dev:443 \
-servername minifishpc01.ronverbs.dev \
-CAfile /etc/ssl/ronverbs-local-ca.crt </dev/null
Near the end of the output, look for:
Verify return code: 0 (ok)
Test the HTTP-to-HTTPS redirect:
curl -I http://minifishpc01.ronverbs.dev/
Test HTTPS using the private CA explicitly:
curl --cacert /etc/ssl/ronverbs-local-ca.crt \
-I https://minifishpc01.ronverbs.dev/
Finally, open:
https://minifishpc01.ronverbs.dev/
9. Check certificate expiration later
Check the certificate file directly:
openssl x509 \
-in /etc/ssl/minifishpc01.ronverbs.dev.crt \
-noout -subject -issuer -dates
Check whether it will expire within 30 days:
openssl x509 \
-in /etc/ssl/minifishpc01.ronverbs.dev.crt \
-noout -checkend 2592000
When it is time to renew, create and sign a new server certificate with the existing CA. The browsers do not need the CA imported again as long as the same CA is reused and remains trusted.
Troubleshooting checklist
If the page does not open cleanly, check the following in order:
minifishpc01.ronverbs.devresolves to127.0.0.1.doas httpd -nreportsconfiguration OK.httpdis running.Ports 80 and 443 are listening on loopback.
The server certificate contains
DNS:minifishpc01.ronverbs.devas a Subject Alternative Name.The server certificate verifies against
ronverbs-local-ca.crt.The private key path in
/etc/httpd.confis correct.The document-root path is relative to
/var/wwwinside thehttpdchroot.The public CA certificate—not the server certificate and certainly not the private key—was imported under the browser’s trusted authorities.
The browser was completely restarted after importing the CA.
At that point the local site should work with normal trusted HTTPS, leaving only the website itself to cause problems.