One ingress controller (the reverse proxy running in your cluster) behind one Hetzner load balancer can serve many domains, including wildcard subdomains. This is how an agency places dozens of client domains on a single load balancer instead of one per client. The controller routes each request to the correct application by the `Host` header. ## Many hosts, one or many Ingress objects You can lay this out in two ways, and both route identically: - **One `Ingress` with several `host` rules**, when the applications reside in one namespace and one team owns them. - **One `Ingress` per application**, each with its own `host`. This is the usual choice for multi-tenant setups, because each application's route sits alongside the application, in its own namespace, and a tenant modifies only their own object. ```yaml title="ingress-per-tenant.yaml" apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: tenant-a namespace: tenant-a spec: ingressClassName: nginx rules: - host: app.tenant-a.com http: paths: - path: / pathType: Prefix backend: service: name: web port: number: 80 ``` The controller merges every `Ingress` that names its class into one routing table, so tenant A's object and tenant B's object share the load balancer without being aware of each other. ## Wildcard hosts A wildcard rule matches every subdomain at one level: ```yaml rules: - host: "*.tenant-a.com" ``` `*.tenant-a.com` matches `app.tenant-a.com` and `api.tenant-a.com`, but not `tenant-a.com` itself or a deeper `a.b.tenant-a.com`. Add an exact `host` rule for the bare domain if you need it. ## Certificates: SNI and wildcards When a client connects, it sends the hostname it wants in the TLS handshake (SNI, Server Name Indication). The controller uses that name to select the matching certificate from the Secrets its `Ingress` objects reference. Many domains on one load balancer therefore each use their own certificate, selected per connection. Each certificate is held and served the same way as for a single host; see [Terminate TLS at the ingress](/docs/hetzner/apalla/network/expose/terminate-tls-at-ingress). A wildcard certificate for `*.tenant-a.com` covers every subdomain with one certificate. Note one requirement: a certificate authority will only issue a wildcard after a **DNS-01** challenge, which proves you control the domain by writing a TXT record, rather than the HTTP-01 challenge that ordinary certificates use. Configure cert-manager with access to your DNS provider so it can solve DNS-01. See [Set up a Let's Encrypt ClusterIssuer](/docs/hetzner/apalla/network/dns-certs/lets-encrypt-clusterissuer). ### One DNS record for every subdomain A wildcard also simplifies DNS. Point a wildcard record, `*.tenant-a.com`, at the load balancer once (a CNAME to the load balancer's hostname, or an `A` and `AAAA` to its addresses), and every subdomain resolves to your ingress with no new record per application. Paired with a wildcard certificate, you add `api.tenant-a.com` or `app.tenant-a.com` by creating an `Ingress` alone: no DNS change, no new certificate. [external-dns](/docs/hetzner/apalla/network/dns-certs/automatic-dns-with-external-dns) can manage these records for you. ### Delegate the DNS-01 challenge with a CNAME DNS-01 requires cert-manager to write a `_acme-challenge` TXT record in your zone, which requires granting it API access to that zone. Keep that access limited by delegating only the challenge: add a CNAME from `_acme-challenge.tenant-a.com` to a record in a separate, less-privileged zone, and give cert-manager credentials for that zone alone. With `cnameStrategy: Follow` on the solver, cert-manager follows the CNAME and writes the TXT record in the delegated zone, so your main zone's API token is never exposed to the cluster. ## Isolate tenants by namespace Give each tenant a namespace and keep their `Ingress`, Services, and certificate Secrets in it. A certificate Secret is only readable in its own namespace, so one tenant's key is never stored where another tenant's application can read it. RBAC on the namespace decides who can edit which routes. ## A default backend for unmatched hosts A request whose `Host` matches no rule reaches the controller's default backend, which returns a 404. Set a default backend that returns a plain "not found" page rather than revealing the name of whichever application happens to be first, so a stray or probing request learns nothing about your tenants. > [!TIP] > The Gateway API serves the same many-domains setup with a `Gateway` and per-application `HTTPRoute` objects, and gives each tenant their own route to own. See [Route traffic with the Gateway API](/docs/hetzner/apalla/network/expose/gateway-api). ## Next - [Terminate TLS at the ingress](/docs/hetzner/apalla/network/expose/terminate-tls-at-ingress) for how one controller holds every host's certificate. - [Set up a Let's Encrypt ClusterIssuer](/docs/hetzner/apalla/network/dns-certs/lets-encrypt-clusterissuer) to issue wildcard certificates with DNS-01. For how agencies run many client clusters on Syself Autopilot, see [Syself for agencies](/docs/hetzner/apalla/security/multi-tenant-isolation-for-agencies).