In a Kubernetes cluster, you can route HTTP and HTTPS traffic using the Kubernetes Gateway API. A Gateway replaces the traditional Ingress Controller and gives you a central place for SSL (TLS) termination, routing rules and security.
In this guide, you’ll configure a Kubernetes Gateway step by step for a simple demo application. First you deploy an app and Service, then you configure a Gateway and HTTPRoute and add SSL security with a TLS certificate (for example via Cert-manager and Let’s Encrypt). At the end, you’ll test whether HTTPS works correctly.
For this guide you’ll need:
- A Kubernetes cluster.
- A Gateway controller such as Traefik or Cilium. For example, follow our Traefik guide first.
- Cert-manager: the Gateway API’s HTTPS listeners only support Kubernetes Secrets (not .json); Cert-manager uses Secrets, Traefik uses .json. For this reason, you’ll need Cert-manager in this setup to generate SSL certificates.
- Kubectl
- A domain name whose DNS points to the gateway (Traefik or Cilium).
What is a Kubernetes Gateway?
The Kubernetes Gateway API introduces new resource types, such as GatewayClass, Gateway and HTTPRoute. These resources describe how traffic flows from outside into your cluster and which Services are allowed to receive that traffic. Unlike a classic Ingress, the Gateway API clearly separates the role of infrastructure management (GatewayClass/Gateway) and application routing (HTTPRoute).
Broadly speaking, a Gateway setup looks like this:
- A GatewayClass describes which type of gateway controller you use (for example Traefik, NGINX or Cilium).
- A Gateway defines one or more listeners (for example HTTP on port 8000 and HTTPS on port 8443) with (sub)domains (hostnames) and TLS settings.
- An HTTPRoute maps (sub)domains and paths to Services in a namespace.
In this tutorial, you’ll create your own Gateway in a separate namespace, configure an HTTPRoute and add SSL termination to the Gateway. This means you don’t have to manage TLS certificates per app, but centrally on the Gateway.
Deploying a demo application behind the Gateway
TL;DR: the quickest copy-and-paste
Create a file, for example gateway-demo.yaml, with the contents below.
- At the very least, replace demo.voorbeeld.nl with your own domain and, if you wish, the namespace name as well:
apiVersion: v1
kind: Namespace
metadata:
name: demo-gateway
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: demo-app
namespace: demo-gateway
spec:
replicas: 2
selector:
matchLabels:
app: demo-app
template:
metadata:
labels:
app: demo-app
spec:
containers:
- name: demo-app
image: traefik/whoami
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: demo-app
namespace: demo-gateway
spec:
selector:
app: demo-app
ports:
- port: 80
targetPort: 80
protocol: TCP
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: demo-gateway
namespace: demo-gateway
spec:
gatewayClassName: traefik
listeners:
- name: web
protocol: HTTP
port: 8000
hostname: demo.voorbeeld.nl
allowedRoutes:
namespaces:
from: Same
- name: websecure
protocol: HTTPS
port: 8443
hostname: demo.voorbeeld.nl
tls:
mode: Terminate
certificateRefs:
- kind: Secret
name: demo-gateway-tls
allowedRoutes:
namespaces:
from: Same
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: demo-route-redirect
namespace: demo-gateway
spec:
parentRefs:
- name: demo-gateway
sectionName: web
hostnames:
- demo.voorbeeld.nl
rules:
- matches:
- path:
type: PathPrefix
value: /
filters:
- type: RequestRedirect
requestRedirect:
scheme: https
statusCode: 301
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: demo-route
namespace: demo-gateway
spec:
parentRefs:
- name: demo-gateway
sectionName: websecure
hostnames:
- demo.voorbeeld.nl
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: demo-app
port: 80
Next, create a namespace for your gateway and apply the .yaml file:
kubectl create namespace demo-gateway
kubectl apply -f gateway-demo.yaml
You’ll need an application behind the Gateway to test your configuration. In this example, you’ll use a simple HTTP ‘whoami’ service on port 80 with a Deployment and Service. The app runs as a container in the cluster; for more background, read our explanation of what a container image is.
Step 1
Create a separate namespace for the demo app and the Gateway resources:
kubectl create namespace demo-gatewayThe demo-gateway namespace helps to logically separate the demo resources from the rest of your cluster.
Step 2
Create the file demo-app.yaml on your computer/laptop with the following contents:
apiVersion: v1
kind: Namespace
metadata:
name: demo-gateway
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: demo-app
namespace: demo-gateway
spec:
replicas: 2
selector:
matchLabels:
app: demo-app
template:
metadata:
labels:
app: demo-app
spec:
containers:
- name: demo-app
image: traefik/whoami
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: demo-app
namespace: demo-gateway
spec:
selector:
app: demo-app
ports:
- port: 80
targetPort: 80
protocol: TCPThe Deployment starts two replicas of the traefik/whoami container (a small HTTP service that returns request information) and the Service makes these pods reachable on port 80 within the namespace.
Step 3
Deploy the demo application:
kubectl apply -f demo-app.yamlThen check that the pods are running:
kubectl get pods -n demo-gatewayOnce the pods are in the Running state, your backend is ready to be exposed via the Gateway.
Step 4
Now that the backend is running, first configure a GatewayClass (often already present). In this example, we’ll assume a Gateway controller such as Traefik that supports the Gateway API.
Check whether (and which) GatewayClasses are already present in your cluster:
kubectl get gatewayclassIf your provider or gateway controller has already created a GatewayClass (for example traefik), use that name in your Gateway and continue to step 5.
Don’t see a GatewayClass yet? Then add one yourself. For example, if you want to define a GatewayClass for Traefik, create a file gatewayclass.yaml with the following contents:
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: traefik
spec:
controllerName: traefik.io/gateway-controllerAdjust the name if your controller expects a different GatewayClass (you can find this in the documentation for the GatewayClass you’re using). The controllerName value identifies which controller manages Gateways of this type; in this example, that’s the Traefik Gateway controller.
kubectl apply -f gatewayclass.yamlWith this step, you link the Gateway API to your chosen gateway controller.
Step 5
Create a .yaml file with the definition of the actual Gateway for HTTP and HTTPS traffic, for example demo-gateway-listener.yaml, with the contents below.
Use your own domain instead of demo.voorbeeld.nl, the correct namespace (here demo-gateway) and the GatewayClass name (traefik in the example):
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: demo-gateway
namespace: demo-gateway
spec:
gatewayClassName: traefik
listeners:
- name: web
protocol: HTTP
port: 8000
hostname: demo.voorbeeld.nl
allowedRoutes:
namespaces:
from: Same
- name: websecure
protocol: HTTPS
port: 8443
hostname: demo.voorbeeld.nl
tls:
mode: Terminate
certificateRefs:
- kind: Secret
name: demo-gateway-tls
allowedRoutes:
namespaces:
from: Same
Step 6
Deploy the Gateway:
kubectl apply -f demo-gateway-listener.yamlThis Gateway listens on port 8000 for HTTP traffic to demo.voorbeeld.nl and on port 8443 for HTTPS traffic. The allowedRoutes.namespaces.from: Same setting restricts routes to the same namespace as the Gateway (demo-gateway), which is safer in multi-tenant environments.
Step 7
Create an HTTPRoute that maps the (sub)domain/hostname and the path to the demo Service. For example, create the file httproute.yaml with the following contents:
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: demo-route
namespace: demo-gateway
spec:
parentRefs:
- name: demo-gateway
sectionName: websecure
hostnames:
- demo.voorbeeld.nl
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: demo-app
port: 80The parentRefs link the route to the demo-gateway Gateway. All paths under / are sent to the demo-app Service on port 80.
Step 8
Apply the HTTPRoute to your Kubernetes cluster:
kubectl apply -f httproute.yamlThen check whether the Gateway and HTTPRoute are ‘Ready’:
kubectl get gateway -n demo-gateway
kubectl get httproute -n demo-gatewayUse kubectl describe for more details if the status is not ‘Programmed’ or ‘Accepted’. Any messages in the conditions provide a clue as to what’s wrong (for example, an unknown GatewayClass or (sub)domain/hostname).
Step 9
Now that HTTP works, add SSL security. In Kubernetes, that usually means TLS termination on the Gateway (via the ClusterIssuer created with Cert-manager): the Gateway presents the certificate to the client and forwards the traffic as unencrypted HTTP to the backend. The certificate is stored in a Secret in the same namespace as the Gateway.
Create a Certificate resource so that Cert-manager automatically requests a certificate for your domain. Use the name of your (Cluster)Issuer, for example letsencrypt-issuer. Create the file certificate.yaml with the following contents:
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: demo-cert
namespace: demo-gateway
spec:
secretName: demo-gateway-tls
issuerRef:
name: letsencrypt-issuer
kind: ClusterIssuer
dnsNames:
- demo.voorbeeld.nlThe dnsNames and secretName fields must match your (sub)domain/hostname and the name you’ll use in the Gateway in a moment. Cert-manager stores the issued certificate and the private key in the demo-gateway-tls Secret.
Step 10
Apply the certificate.yaml you just created to your Kubernetes cluster:
kubectl apply -f certificate.yamlTo be safe, check that the certificate has been created successfully:
kubectl describe certificate demo-cert -n demo-gatewayPay attention to the conditions; if the status is Ready and there are no DNS or HTTP challenge errors, the Secret containing the certificate is available. With -n demo-gateway you specify which namespace the Certificate is in.
Step 11
Finally, test whether HTTPS works using, for example, a curl command, or navigate to the (sub)domain in your browser:
curl -v https://demo.voorbeeld.nlYou should see a TLS handshake followed by the output from the whoami service. If your browser no longer shows warnings about an unsafe certificate, your SSL security is configured correctly.
Troubleshooting: common mistakes
Still not able to reach your application via HTTPS? The checks below will help you resolve the most common issues.
Step 1
Verify that the backend Service and pods are working:
kubectl get svc -n demo-gateway
kubectl get pods -n demo-gateway
kubectl logs -l app=demo-app -n demo-gatewayIf the pods are crashing or not receiving traffic, fix that first before looking further at the Gateway configuration.
Step 2
Check DNS and reachability:
nslookup demo.voorbeeld.nl
ping demo.voorbeeld.nlIf the (sub)domain/hostname does not point to the correct IP address or returns an error, check your domain’s DNS settings and your nameservers.
Step 3
Check the status of the Gateway and HTTPRoute:
kubectl describe gateway demo-gateway -n demo-gateway
kubectl describe httproute demo-route -n demo-gatewayLook for conditions such as Programmed and Accepted and any error messages (for example, an unknown GatewayClass, an incorrect (sub)domain/hostname or a missing listener). These fields usually indicate straight away where the configuration is conflicting.
Step 4
Check that the TLS certificate and the Secret exist and are ‘Ready’:
kubectl get certificate -n demo-gateway
kubectl get secret demo-gateway-tls -n demo-gatewayIf the Secret is missing or the certificate is not Ready, view Cert-manager’s events with kubectl describe certificate and resolve any HTTP or DNS challenge issues (for example, incorrect DNS records or firewall rules).
In this guide, you set up a Kubernetes Gateway for a demo application, configured HTTP and HTTPS listeners, linked a TLS certificate via Cert-manager and added an HTTP->HTTPS redirect. With this approach, you centralise your SSL security, make routing rules easier to manage, and you’re ready to use more advanced Kubernetes Gateway API features, such as multiple hostnames, per-environment paths and additional security filters.