Serve multiple domains with TLS
One load balancer, one public address, port 443, several domains going to different places. Each name gets its own certificate, obtained and renewed for you.
The model
Section titled “The model”A listener is a routing rule, not a socket. It carries a port and a hostname, so several listeners share a port:
| Port | Hostname | Target |
|---|---|---|
| 443 | app.example.com | Kubernetes cluster, NodePort 30080 |
| 443 | s3.example.com | storage nodes |
| 443 | api.example.org | servers 10.x.y.10:8080, 10.x.y.11:8080 |
Under the hood this is one HAProxy front end with three routing rules and three back ends. You never see that.
Prerequisites
Section titled “Prerequisites”- Each domain is in your account with DNS hosted here
- A load balancer, public or internal
1. Add the first listener
Section titled “1. Add the first listener”Ağ → Yük Dengeleyiciler, open the load balancer, Dinleyici Ekle (Add listener).
| Field | Value |
|---|---|
| Port | 443 |
| Alan adı | app.example.com |
| TLS burada sonlansın | ticked |
| Hedef | cluster or servers |
2. Add the rest
Section titled “2. Add the rest”Same port, different hostname. The panel accepts it. Each one gets its own certificate record.
3. Point DNS at the load balancer
Section titled “3. Point DNS at the load balancer”For each name, either create an A record pointing at the load balancer’s address, or use
the LB control on the DNS records screen, which creates the listener and points the
record in one step.
4. Watch the certificates
Section titled “4. Watch the certificates”Each listener shows its own certificate status. Issuance takes minutes: the challenge record has to be published and seen by public resolvers before Let’s Encrypt is asked to check.
Rules to know
Section titled “Rules to know”One protocol per port. All listeners on 443 are either all https or all tcp. The
API refuses a mix, because HAProxy can only do it through a two-stage loop-back
arrangement — technically possible, impossible to reason about from a panel.
A default is optional. A listener with an empty hostname is the fallback for that port. With no default, a request for an unknown name is rejected rather than silently served by whichever backend happens to be first. That is usually what you want.
tcp with a hostname routes by SNI. Nothing is decrypted, so only the TLS handshake’s
server name is available. It works for TLS clients and only for them.
Reusing a hostname reuses its certificate. Putting the same name on a second listener does not request a duplicate — which also keeps you inside Let’s Encrypt’s limit of five identical certificates per week.
Adding Cloudflare’s proxy on top
Section titled “Adding Cloudflare’s proxy on top”Turn on Proxy’li for any of these records if you want to hide your origin address. Nothing breaks:
- The certificate keeps renewing, because the ACME challenge is a
TXTrecord andTXTrecords are never proxied. - The panel never writes the proxy flag — when it points a record at a load balancer it reads your existing setting and writes it back unchanged.
Set Cloudflare’s SSL mode to Full (strict); your load balancer presents a valid certificate, so strict validation passes. See Using zone9 with Cloudflare.
Troubleshooting
Section titled “Troubleshooting”“bu portta zaten bir dinleyici var”. You used the same port and the same hostname twice, or left the hostname empty on a port that already has a default.
“bir portun bütün dinleyicileri aynı protokolde olmalı”. You are adding an https
listener to a port that already has a tcp one, or the reverse.
“alan adının DNS’i bizde değil”. The domain is in your account but its DNS is hosted elsewhere. Certificate issuance needs to write a challenge record.
The certificate keeps failing. Read the error on the listener. After fixing the cause use the explicit retry, which clears the exponential backoff — otherwise the next attempt may be hours away, by design.