Stunnel PKI — Patterns for New Client Hosts

Purpose: generalises the single-CA mutual-TLS approach in ssl/pki into a repeatable pattern for onboarding new client hosts or new trust relationships, beyond the original pomelo-only setup. Read this when adding a new host that needs to connect into an existing stunnel service, or when wiring up a second independent trust domain (e.g. a service-specific tunnel like VNC).

Core Pattern

Whichever host accepts connections is the CA for its own inbound domain. Its self-signed root cert doubles as both its TLS server identity and its CA (CAfile points at its own cert — see ssl/pki Trust Model). Anyone allowed to connect in needs a client certificate signed by that root.

Two valid ways to provision the client side, depending on whether the new host should share an existing identity or get its own:

Option A — reuse an existing client identity

If the new client host is equally trusted and belongs to the same operator (e.g. a second controller acting as "pomelo"), just copy the existing client cert/key/CA files to it. This is what ssl/pki documents under "Setting Up a New Controller", and what was done when wiring the kelp VNC tunnel on 2026-07-22 — kelp's VNC stunnel was repointed to kelp's existing server.pem/server.key (the same CA already used by socks5h/notes), and the client side reused pomelo's existing /etc/stunnel/cert.pem/key.pem. No new key material was generated or copied.

Option B — fresh per-host client certificate

If the new client host should have its own distinct, individually-revocable identity, generate a fresh keypair on the new host and get it signed by the CA — without the CA's private key ever leaving the host that holds it. The private key for the new client never travels; only the CSR crosses, and only the signed cert comes back.

1. On the new client host, generate a private key and CSR:

openssl genrsa -out client.key 4096
openssl req -new -key client.key -out client.req \
  -subj "/C=UK/L=Winterbourne/CN=<new-host-identity>"

2. Copy only client.req (not the key) to the host holding the CA's private key (pomelo, for the main domain — see ssl/pki Certificate Inventory).

3. Sign it there against the CA:

openssl x509 -req -days 3653 -in client.req \
  -CA CA/cert -CAkey CA/key -set_serial <next-unused-serial> -out client.cert

4. Copy client.cert back to the new host, along with a copy of the CA's public cert (CA/cert) for verifying the server side. Never copy CA/key off the host that holds it.

5. On the new client host, write a stunnel client section pointing at the new cert/key and the CA cert, following the client-side example in ssl/pki. Use a distinct -set_serial value from any other cert signed by that CA.

Independent Trust Domains (Hub-and-Spoke)

Each accepting host can be the CA root for its own inbound connections, independent of other domains. Example: kelp holds a separate CA/cert/key set (kelp-client-CA.pem, kelp-client-cert.pem, kelp-client-key.pem) used only when kelp itself connects outward as a client to gravlax — this is unrelated to the main pomelo-rooted CA that secures connections into kelp. A single host can therefore hold multiple distinct client identities, one per domain it connects out to, alongside its own CA for the domain it accepts into.

Related

ssl — overview and port map.

ssl/pki — certificate hierarchy, file locations, existing new-controller procedure.

ssl/create-certs — openssl commands for the main CA/client cert pair.

version 1  ·  created 2026-07-23  ·  updated 2026-07-23  ·  tags ssl, tls, stunnel, pki, patterns