Skip to content
Beta

Expose HTTP services

Every Kubernetes cluster can serve your websites and APIs over HTTPS through its managed gateway. Attach an HTTPRoute or an Ingress to it, and the platform takes care of the hostname and the certificate, with no load balancer to set up.

Websites and APIs (HTTP and HTTPS) → use the managed gateway, on the cluster's hostname or on your own domain through a CNAME record. Everything else (databases, TCP or UDP services) → a Service of type LoadBalancer, on a port of its own.

Every cluster has the managed gateway, switched on by default. If the cluster's Services & routes section does not show Managed gateway, it has been switched off for your workspace: contact support.

How it works

The platform creates a Gateway named sahabti in the cluster's sahabti-gateway namespace and keeps it there: you attach routes to it, and you do not edit it. Visitors connect over HTTPS to the platform's edge, which holds the certificates, and their requests reach the gateway as plain HTTP; the gateway then sends each one to the Service your route names. The gateway uses no load balancer, and none of your workspace's load-balancer quota.

Quick start

Save this as hello.yaml: a small web server, its Service, and a route to it.

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello
spec:
  replicas: 2
  selector:
    matchLabels:
      app: hello
  template:
    metadata:
      labels:
        app: hello
    spec:
      containers:
        - name: hello
          image: nginx:1.27-alpine
          ports:
            - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: hello
spec:
  selector:
    app: hello
  ports:
    - port: 80
      targetPort: 80
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: hello
spec:
  parentRefs:
    - name: sahabti
      namespace: sahabti-gateway
  rules:
    - backendRefs:
        - name: hello
          port: 80
bash
kubectl apply -f hello.yaml

The route names no hostnames, so it answers on the cluster's own hostname, <cluster>.sahabti.app, which the cluster's Services & routes shows under Managed gateway. Once that hostname shows Live, open it:

bash
curl https://<cluster>.sahabti.app/

A route can live in any namespace, as long as its parentRefs names the gateway together with its namespace, as here.

Use an Ingress

Ingresses work too, which suits Helm charts and manifests that already use them. Give them the class sahabti:

yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: shop
spec:
  ingressClassName: sahabti
  rules:
    - host: shop.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: hello
                port:
                  number: 80
  • sahabti is the cluster's default IngressClass, unless another class was already the default.
  • The platform turns each Ingress of this class into HTTPRoutes and keeps them in sync with it.
  • Its tls section is ignored, because HTTPS is handled at the edge, and the Ingress gets an event saying so.
  • Its status shows the cluster's hostname, which kubectl get ingress lists as its address.

The host must be a hostname the cluster holds: see Hostnames.

Hostnames

A cluster serves only the hostnames it holds:

  • its own hostname, <cluster>.sahabti.app;
  • your own domain, such as shop.example.com, proven by a CNAME record;
  • a root domain, such as example.com, bound through a verified workspace domain;
  • extra names under sahabti.app.

The cluster's own hostname

Every cluster has one, <cluster>.sahabti.app, shown in its Services & routes, and it needs no setup. A route without hostnames answers on it, and on any of the cluster's hostnames that no other route claims.

Your own domain

Serving shop.example.com from the cluster takes two steps.

  1. Add the hostname. In the cluster's Services & routes, under Managed gateway → Use your own domain, enter shop.example.com and select Add hostname.
  2. Point a CNAME record at the cluster. At your DNS provider, create this record. The console shows it with your cluster's hostname filled in, ready to copy.
TypeNameValue
CNAMEshop.example.com<cluster>.sahabti.app

Then name the hostname in a route, as for any other hostname:

yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: shop
spec:
  parentRefs:
    - name: sahabti
      namespace: sahabti-gateway
  hostnames:
    - shop.example.com
  rules:
    - backendRefs:
        - name: hello
          port: 80

The platform checks DNS every minute. As soon as the record names the cluster, it issues the certificate and serves the hostname; its status in the console moves from Waiting for DNS to Issuing certificate to Live, usually within a few minutes. The first step is optional: a hostname that one of the cluster's routes already uses is listed on its own, with the same record to create.

Good to know:

  • No TXT record and no workspace domain is needed: the CNAME record is what proves the hostname is yours.
  • If your DNS provider offers to proxy the record, keep it DNS only, so the CNAME stays visible.
  • Keep the record in place. The platform keeps checking it; if it stops pointing at the cluster, the hostname shows CNAME missing, and an hour later it is no longer served. Restore the record and it is served again. A hostname that no route uses any more is then removed.
  • One hostname belongs to one app, instance or cluster on the whole platform, and it goes to the first workspace that proves it. A hostname another workspace has only added, without its record pointing there, never blocks you.
  • Wildcard hostnames, such as *.example.com, are not supported.

A root domain

A root domain such as example.com cannot hold a CNAME record. To serve it from the cluster:

  1. On your workspace's Domains page, add example.com and verify it with the TXT record shown there.
  2. In the cluster's Services & routes, under Managed gateway → More ways to add a hostname, bind example.com with Bind a verified domain.
  3. At your DNS provider, point example.com at apps.sahabti.app, with an ALIAS, ANAME or flattened CNAME record if your provider offers one.

Keep the TXT record in place. The platform checks it again every day. As soon as the record is missing, the workspace's owners and admins get an email and the Domains page asks you to re-add it; if it is still gone three days later, the hostnames under the domain are suspended and another workspace may verify the domain. A domain verified before these daily checks began gets 30 days instead of 3 the first time its record goes missing.

Extra names

To serve more sites under sahabti.app, add extra names in the cluster's Services & routes, under Managed gateway → More ways to add a hostname. Each one is served as <name>--<cluster>.sahabti.app, so the name shop becomes shop--<cluster>.sahabti.app.

A name is 3 to 40 lowercase letters, digits and single dashes. Everything before .sahabti.app must fit in 63 characters, so a cluster with a long name leaves less room for it.

To send an extra name to a particular Service, put the full hostname in a route's hostnames or an Ingress's host.

HTTPS and the client's address

HTTPS needs nothing from you. TLS ends at the platform's HTTPS edge, which issues and renews the certificates, and requests reach the cluster as plain HTTP on the gateway, so your Services serve plain HTTP and need no certificate of their own. The client's address arrives in the X-Forwarded-For header.

Check the status

In the cluster's Services & routes, Managed gateway lists each hostname with its status, and says what to do next:

StatusMeans
LiveServed over HTTPS
No route yetReady, but no HTTPRoute or Ingress names it yet
Waiting for DNSIts CNAME record does not point at the cluster yet; the record to create is shown next to it
Issuing certificateThe record is in place and the certificate is on its way
CNAME missingIts record stopped pointing at the cluster; it stops being served an hour later unless the record is back
PendingNot served yet, with the reason, such as waiting for the gateway to start
Not allowedA route uses it, but the cluster cannot hold it, with the reason
OffThe managed gateway is turned off
ErrorSetting it up failed, with what to do

A route that uses a hostname the cluster does not hold is not served, and gets a HostnameNotRouted event:

bash
kubectl describe httproute shop

Troubleshooting

A hostname stays on Waiting for DNS

Check the record from your own machine:

bash
dig +short CNAME shop.example.com

It must answer with the cluster's hostname, <cluster>.sahabti.app., or one of its extra names.

  • A record pointing at apps.sahabti.app does not say which cluster you mean: point it at the cluster's own hostname.
  • A record your DNS provider proxies answers with addresses instead of the CNAME: make it DNS only.
  • A new or changed record can take as long as its TTL to reach every resolver.

A hostname shows Not allowed

The note next to it says why:

  • Another workspace proved it first. If the hostname is yours, point its CNAME record at your cluster: the other workspace stops holding it an hour after its record stops pointing at its cluster, or three days after a verified domain's TXT record is gone, and your cluster takes it over.
  • A root domain. Bind it through a verified domain.
  • The platform's own domain. Add it as an extra name instead.
  • The hostname limit. Remove a hostname the cluster no longer needs.

A hostname stays on No route yet

The platform serves it, but no route names it, so the gateway has nowhere to send the request. Add it to an HTTPRoute's hostnames or an Ingress's host.

The certificate does not arrive

A CAA record on your domain may not allow the certificate authority. Allow letsencrypt.org, pki.goog and ssl.com in it, or remove the CAA record, then remove the hostname and add it again.

The gateway shows a problem

The console names it. The most common is a Service of yours using node port 30080, which the managed gateway needs on every node: give that Service another node port.

Limits

Free planPaid plans
Hostnames per cluster: your own domains, bound domains and extra names together20100

The managed gateway itself uses none of your workspace's load balancers.

Leave node port 30080 free: the managed gateway uses it on the cluster's nodes, and a Service of yours that takes it stops the gateway. The console then shows an error naming that Service.

Updates

We keep the managed gateway up to date, security fixes included, with nothing to do on your side. An update replaces the gateway's proxy one copy at a time, and the new copy takes requests before the old one stops, so your hostnames stay served. A connection held open across an update, such as a WebSocket or a long download, may need to reconnect. After each update the platform checks the cluster and puts the previous version back if routes it accepted before are no longer accepted. The gateway's own objects in the cluster are managed by the platform, and changes made to them are reverted.

Turn the managed gateway off

In the cluster's Settings, switch Managed gateway off. This withdraws every hostname from the edge and removes the gateway's objects from the cluster. Your HTTPRoutes and Ingresses stay, but are no longer served. Switch it on again to bring it back.

Run your own gateway or ingress controller

The managed gateway is a default, not a requirement. To serve HTTP with a controller of your own:

  1. In the cluster's Settings, switch Managed gateway off, so two controllers never answer for the same Ingresses. You can also keep it on and give your Ingresses a class of your own.
  2. Install the Gateway API implementation or ingress controller you prefer, and expose it with a Service of type LoadBalancer. It counts against your workspace's load-balancer quota and answers on a public port of your workspace's shared IP; a cluster's load balancers cannot have a dedicated IP yet, so ports 80 and 443 are not available to them.
  3. To have Ingresses without a class use it, mark your IngressClass as the default with the annotation ingressclass.kubernetes.io/is-default-class: "true". The managed gateway's class sahabti is made the default only when no other class is.
  4. Point your domain at the load balancer's address and manage its certificates yourself: the platform's edge and its certificates serve only the managed gateway.

When to use a load balancer instead

Use a Service of type LoadBalancer for anything that is not a website or an API, such as databases, game servers, or raw TCP or UDP, or when you need to terminate TLS yourself. Each one counts against your workspace's load-balancer quota: see A load balancer for a Service. On your workspace's shared IP it answers on a public port of its own, so clients connect to its hostname with that port, such as prod-default-db-e5f6a7b8-q2w7ze.lb.sahabti.app:40002; HTTPS without a port is the managed gateway's.

The gateway class sahabti is still there for running a Gateway of your own behind a load balancer, as in Route HTTP by hostname. Mind the names: routes attached to the Gateway sahabti in sahabti-gateway, and Ingresses of class sahabti, use the managed gateway, while a Gateway you create with gatewayClassName: sahabti runs behind a load balancer.