Routing SSL / 443 to backend servers on OpenBSD with Relayd

by JChase2

I have a physical thinkserver box, a laptop, and a desktop. On the thinkserver, I have alpine linux acting as a hyperviser, a VPS running OpenBSD for web hosting, a VPS running slackware for backups, and another VPS running slackware for Nextcloud and Plex. (Basically a cloud server). I have a router running DD-WRT, which obviously only has one external IP, and port forwards to the various servers and services I have running.

Now to complicate things slightly, I also have a single domain I use for my personal site, and my resume / web dev portfolio, along with nextcloud and plex. I use SSL on my site and its subdomains. Traffic running over SSL encrypts headers, so you can’t get the intended hostname at the router level. All I can do is listen for traffic on specific ports, and forward them to a specific IP address. The various VPS’s I’m running have different internal IP’s, obviously.

So, how to run my nextcloud instance on a subdomain on the same port as my regular site? By using a reverse proxy. Luckily OpenBSD comes with a reverse proxy, relayd. Relayd also can’t access headers to forward to the inteded domain unless it decrypts the ssl traffic. Relayd can decrypt the traffic if you supply keys for the domain and they’re stored and named correctly in /etc/ssl, and /etc/ssl/private. Here’s my config with added comments:

#These aren't necessary but they're nice. 
table <nextcloudservice> { }
table <localhostservice> { }

# SSL protocol
# This matches the HOST and sets up a forward to the inteded internal
# domain.
http protocol "https" {
  return error
  match request header "Host" value "" forward 
  to <nextcloudservice>
  match request header "Host" value "" forward to 
  match request header "Host" value "" forward to 

 # Specifies keypairs for domains
 tls keypair
 tls keypair
 tls keypair

#The actual relay that uses the https protocol block above.
relay "secure_proxy" {
  protocol https
  listen on port 443 tls

  #Forwards to my other VPS for nextcloud, 
  #and to localhost for local services on this VPS.
  forward to <nextcloudservice> port 80
  forward to <localhostservice> port 80
  forward to <localhostservice> port 80

A couple of things to note here, everything is forwarded unencrypted. This openBSD VPS gets everything sent to my router on 443, decrypts it with the keys, and forwards it unencrypted via port 80. Since I control my network and the thinkserver is wired to the router, I don’t need internal encryption. If you do, relayd supports that, but I didn’t bother.

The keys have to be named very specifically. Here’s the documentation regarding key naming:

The relay will attempt to look up a private key in /etc/ssl/private/name:port.key and a public certificate in /etc/ssl/name:port.crt, where port is the specified port that the relay listens on. If these files are not present, the relay will continue to look in /etc/ssl/private/name.key and /etc/ssl/name.crt. This option can be specified multiple times for TLS Server Name Indication. If not specified, a keypair will be loaded using the specified IP address of the relay as name. See ssl(8) for details about SSL/TLS server certificates.

This initially threw me off quite a bit, as acme-client will name the crt or pem file as name.fullchain.pem, or name.fullchain.crt. I modified my acme-clients config blocks similarly to the following:

domain {
    domain key "/etc/ssl/private/"
    domain full chain certificate "/etc/ssl/"
    sign with letsencrypt
    challengedir "/home/www/acme"

You could also do, and, as that’s the first place relayd will check. I think it’s more readable the way I did it though. Shout out to the OpenBSD misc mailing list for helping me figure that out, as I was completely overlooking the naming here.

Another thing to note is that you need to modify your webserver config for any backend servers to accept port 80. Originally I had everything redirecting to 443. I still do, but all the redirects are on the OpenBSD box for port 80. Nginx / httpd / apache are all configured to use port 80 internally, because that’s what relayd will be routing traffic through internally. This is easy, just remove your *443 config and keys, .well-known, etc, and use port 80. Then set up your acme-client or however you have SSL configured via OpenBSD. All SSL certs will be handled from your relayd box.

Hopefully this helps someone.


By: Glenda (Thu Apr 1 14:13:28 EDT 2021)