Server Name Indication (SNI) is for selecting a service, not a certificate

11 May, 2026

I added support for Server Name Indication (SNI) to the Gemini Protocol server I am working on, Ground Control, so that multiple capsules can be served by the same server with their own certificates. This is something Ground Control lost when I switched from OpenSSL to BearSSL. BearSSL explicitly excludes this feature because it's not a core part of the TLS protocol.

The paradigm in OpenSSL is that the application developer writes a function that takes as a parameter a domain name and returns the key that should be used for that domain. This function is attached to the OpenSSL object, and is called by the library during the TLS handshake to configure a new connection.

In order to do the same with BearSSL you:

This was the first implementation: I copied the paradigm pretty directly to use with BearSSL.

Ideally all of this would be wrapped up in the C function that performs the BearSSL handshake---this is just an extension of the handshake after all. Unfortunately, because Ground Control is configured in the Wren scripting language, the certificate selection function will be written in Wren and not C, and Wren does not support calling from Wren into C and then back into Wren again. The handshake became 3 C functions with descriptive names like "handshake", "prehandshake" and "prehandshake2". At least it could all be hidden inside a single user-facing Wren method.

It was a pretty uninteresting exercise, but a necessary feature.

It also felt like I was doing something I shouldn't be doing, like I'm pulling a fast one on BearSSL. BearSSL lacks a lot of features that libraries like OpenSSL have, and a lot of the time it's features that are easy to misuse. I couldn't wrap my head around why this isn't part of BearSSL and that made me worry that I was doing something dangerous.

Last time I had to bolt a feature onto BearSSL, it was to circumvent certificate authentication. I understand why I had to write a lot of code to do this. It's because it quietly removes a fundamental feature of TLS, authentication. This had me worried that SNI was going to somehow have security implications that I do not understand.

An aside: how can I run two different servers from the same socket?

Around the same time, but unrelated to adding this feature to Ground Control, I started to think about running a server from my home internet connection, and exposing it to the internet proxied through a VPS. The VPS provides a static IP address that is not hidden behind NAT (possibly multiple layers), and also hides my home IP address from visitors.

SSH can proxy: running `ssh -R`, you can listen on a TCP port on the remote server that proxies connections to a socket on the client's local network. On its own, this is enough, but it only works for one server. What I actually wanted is to keep running the same server on the VPS, but also make the computer on my home network available at a different domain. With this solution, each server needs either its own IP address (which costs money) or its own port (which must be given as part of every URL).

This same issue appears if you want to run different Gemini server programs on the same IP address/port combination. A lot of applications written for Gemini come in the form of high-level code that runs in an interpreter embedded in a particular Gemini server. If I want to run Bubble BBS, I have to run GmCapsule. Could I somehow run Bubble on a subdomain, while still hosting my personal capsule with Ground Control?

After meditating on this puzzle for a couple days, it dawned on me that I already had all of the pieces I need. This is what Server Name Indication is for.

SNI proxying

It turns out what I came with is called SNI proxying.

We abstract the steps laid out earlier:

"initiate a connection to that service" might mean to initialize a BearSSL structure, but it might also mean connecting to a socket.

I rewrote the SNI logic in Ground Control to follow this model, and implemented proxying. You could even point one domain to a Gemini Server and another domain to an HTTP server on the same port. But that's probably a bad idea. Slightly more practically, a user of Ground Control could write a different Gemini Server in the Wren scripting language, and run it alongside Ground Control's built-in Gemini server (also written in the Wren scripting language) out of the same socket.

I understand now why this is outside the scope of BearSSL

SNI multiplexing is not something that happens during a TLS stream, it happens once (optionally) at the beginning of the connection. BearSSL doesn't deal with SNI because it all happens before BearSSL even starts. You use that information to decide how BearSSL will be initialized, or even if BearSSL will be involved at all.

By contrast, when OpenSSL runs its SNI code, it has already taken ownership of the socket.

I am adopting interoperability with other Gemini servers as a value guiding the development of Ground Control

SNI proxying is one way this can be achieved. I am not sure that any other Gemini server has this feature, but they really don't need to. SNI proxying is something that can be done by a standalone program, and Ground Control should work fine sitting behind a different SNI proxy. But it's a feature that fits neatly into Ground Control's architecture and it's convenient to not need another server to get it.

It's also got me thinking about the software for Gemini that is tied to a specific server. When I started, I thought this was a good model, and Ground Control can be programmed by writing Wren programs that will only ever by runnable by Ground Control.

I realize now that this only serves to lock you into a particular Gemini server and I think that's a bad thing, even if it's *my* Gemini server.

To that end, I will be adding server-side SCGI support to Ground Control. Any software for Ground Control that is written as a Gemini handler function should also be servable over SCGI, allowing you to use Ground Control to run Gemini applications without being *the* public facing Gemini server.

I will also be exploring other ways that Ground Control can co-exist with the many pre-existing Gemini servers out there.

SCGI makes sense sometimes, but so does SNI proxying

On the web, this problem of application software being tied to a particular HTTP server is solved by reverse proxy. The user facing server accepts an HTTP request from a client, and then passes on (more or less) the same request to the application-specific HTTP server. This paradigm is of limited use for Gemini because there is no way to proxy client authentication.

In between SNI proxying and Gemini reverse proxy, another option is to run a TLS Termination Server. A TLS Termination server will also be able to multiplex based on SNI, but is responsible for handling all of the TLS protocol. The connection to the internal server is not encrypted. This would mean either running a non-standard Gemini server without TLS, or using non-standard TLS Termination that can use TLS on the backend.

TLS Termination ends up looking a lot like a reverse Gemini proxy, except that the user-facing server doesn't know anything about the Gemini protocol. A reverse proxy will not pass on malformed requests and is able to protect against many malicious clients, such as Slow-Lorus type attacks. These protections would need to be present on every server that is being proxied to. This limitation also applies to SNI proxying.

SCGI (and FastCGI) solve the authentication problem. SCGI works like a reverse proxy, but the protocol between the two servers is different. SCGI doesn't support proxying TLS authentication, but it does allow the user facing server to perform user authentication and signal to the SCGI server that this has happened. In practice this is all you need, the only limitation is that it's not cryptographically secure. The SCGI server has to trust what it is told about authentication. If an end user is able to connect to an SCGI server, that user can spoof authentication.

Gemini reverse proxying, TLS termination proxying and SCGI have the authentication issue in the other direction too. In each case, the user facing server is responsible for handling the server credentials. There is no way for the server behind the proxy to identify itself, cryptographically.

SNI proxying is different. The server that is proxied to is the one with the server credentials. This is especially meaningful when the user facing server proxies to a different computer. A single server could proxy to many others that are controlled by different people or groups, and each one is responsible for their own keys. (on the other hand, there are situations where it's more convenient to configure all the different keys in a single place)

Proxied content from gemini://jeffrey.cam/log/sni-multiplexing.gmi (external content)

Gemini request details:

Original URL
gemini://jeffrey.cam/log/sni-multiplexing.gmi
Status code
Success
Meta
text/gemini
Proxied by
kineto

Be advised that no attempt was made to verify the remote SSL certificate.