Set up a built-in Kubernetes gateway with Kong Mesh

Uses: Kong Mesh
TL;DR

Install the Gateway API CRDs, create a GatewayClass and a Gateway to configure the built-in gateway, then create an HTTPRoute and allow traffic to the gateway with a MeshTrafficPermission. To secure your endpoint, generate a certificate and add it to the Gateway.

Prerequisites

You will need Helm, a package manager for Kubernetes.

This guide requires a running Kubernetes cluster that supports the LoadBalancer service type. If you already have a Kubernetes cluster running, you can skip this step. It can be a cluster running locally, like Docker, or in a public cloud like AWS EKS, GCP GKE, etc.

  1. Install Kong Mesh:

    helm repo add kong-mesh https://kong.github.io/kong-mesh-charts
    helm repo update
    helm upgrade \
      --install \
      --create-namespace \
      --namespace kong-mesh-system \
      kong-mesh kong-mesh/kong-mesh
    kubectl wait -n kong-mesh-system --for=condition=ready pod --selector=app=kong-mesh-control-plane --timeout=90s
  2. Apply the demo configuration:

    echo "
    apiVersion: v1
    kind: Namespace
    metadata:
      labels:
        kuma.io/sidecar-injection: enabled
      name: kong-mesh-demo
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: demo-app
      namespace: kong-mesh-demo
    spec:
      ports:
      - appProtocol: http
        port: 5050
        protocol: TCP
        targetPort: 5050
      selector:
        app: demo-app
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: demo-app-v1
      namespace: kong-mesh-demo
    spec:
      ports:
      - appProtocol: http
        port: 5050
        protocol: TCP
        targetPort: 5050
      selector:
        app: demo-app
        version: v1
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: demo-app-v2
      namespace: kong-mesh-demo
    spec:
      ports:
      - appProtocol: http
        port: 5050
        protocol: TCP
        targetPort: 5050
      selector:
        app: demo-app
        version: v2
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: kv
      namespace: kong-mesh-demo
    spec:
      ports:
      - appProtocol: http
        port: 5050
        protocol: TCP
        targetPort: 5050
      selector:
        app: kv
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      labels:
        app: demo-app
        version: v1
      name: demo-app
      namespace: kong-mesh-demo
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: demo-app
          version: v1
      template:
        metadata:
          labels:
            app: demo-app
            version: v1
        spec:
          containers:
          - env:
            - name: OTEL_SERVICE_NAME
              value: demo-app
            - name: OTEL_EXPORTER_OTLP_ENDPOINT
              value: http://opentelemetry-collector.mesh-observability:4317
            - name: KV_URL
              value: http://kv.kong-mesh-demo.svc.cluster.local:5050
            - name: APP_VERSION
              valueFrom:
                fieldRef:
                  fieldPath: metadata.labels['version']
            image: ghcr.io/kumahq/kuma-counter-demo:latest@sha256:daf8f5cffa10b576ff845be84e4e3bd5a8a6470c7e66293c5e03a148f08ac148
            name: app
            ports:
            - containerPort: 5050
              name: http
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      labels:
        app: demo-app
        version: v2
      name: demo-app-v2
      namespace: kong-mesh-demo
    spec:
      replicas: 0
      selector:
        matchLabels:
          app: demo-app
          version: v2
      template:
        metadata:
          labels:
            app: demo-app
            version: v2
        spec:
          containers:
          - env:
            - name: OTEL_SERVICE_NAME
              value: demo-app
            - name: OTEL_EXPORTER_OTLP_ENDPOINT
              value: http://opentelemetry-collector.mesh-observability:4317
            - name: KV_URL
              value: http://kv.kong-mesh-demo.svc.cluster.local:5050
            - name: APP_VERSION
              valueFrom:
                fieldRef:
                  fieldPath: metadata.labels['version']
            image: ghcr.io/kumahq/kuma-counter-demo:latest@sha256:daf8f5cffa10b576ff845be84e4e3bd5a8a6470c7e66293c5e03a148f08ac148
            name: demo-app
            ports:
            - containerPort: 5050
              name: http
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: kv
      namespace: kong-mesh-demo
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: kv
      template:
        metadata:
          labels:
            app: kv
        spec:
          containers:
          - env:
            - name: OTEL_SERVICE_NAME
              value: kv
            - name: OTEL_EXPORTER_OTLP_ENDPOINT
              value: http://opentelemetry-collector.mesh-observability:4317
            - name: APP_VERSION
              valueFrom:
                fieldRef:
                  fieldPath: metadata.labels['version']
            image: ghcr.io/kumahq/kuma-counter-demo:latest@sha256:daf8f5cffa10b576ff845be84e4e3bd5a8a6470c7e66293c5e03a148f08ac148
            name: app
            ports:
            - containerPort: 5050
              name: http
    ---
    apiVersion: kuma.io/v1alpha1
    kind: Mesh
    metadata:
      name: default
    spec:
      meshServices:
        mode: Exclusive
      mtls:
        backends:
        - name: ca-1
          type: builtin
        enabledBackend: ca-1
    ---
    apiVersion: kuma.io/v1alpha1
    kind: MeshTrafficPermission
    metadata:
      name: kv
      namespace: kong-mesh-demo
    spec:
      from:
      - default:
          action: Allow
        targetRef:
          kind: MeshSubset
          tags:
            app: demo-app
            k8s.kuma.io/namespace: kong-mesh-demo
      targetRef:
        kind: Dataplane
        labels:
          app: kv" | kubectl apply -f -
    kubectl wait -n kong-mesh-demo --for=condition=available --timeout=120s deployment --all

To get traffic from outside your mesh with Kong Mesh, you can use a built-in gateway.

With the demo configuration, traffic can only get in the mesh by port-forwarding to an instance of an app inside the mesh. In production, you typically set up a gateway to receive traffic external to the mesh. In this guide we’ll use the Kubernetes Gateway API to add a built-in gateway in front of the demo-app service and expose it publicly.

Install the Gateway API CRDs

  1. Run the following command to install the Kubernetes Gateway API CRDs:
  kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.3.0/standard-install.yaml
  1. Create a GatewayClass resource:
    echo "apiVersion: gateway.networking.k8s.io/v1
    kind: GatewayClass
    metadata:
      name: built-in-gateway
    spec:
      controllerName: gateways.kuma.io/controller" | kubectl apply -f -
  2. Restart the Kong Mesh control plane to apply the changes:
    kubectl rollout restart deployment kong-mesh-control-plane -n kong-mesh-system
    kubectl wait -n kong-mesh-system --for=condition=ready pod --selector=app=kong-mesh-control-plane --timeout=90s

Configure the gateway

  1. Create a Gateway resource to configure the pods that will run the gateway:

    echo "apiVersion: gateway.networking.k8s.io/v1
    kind: Gateway
    metadata:
      name: built-in-gateway
      namespace: kong-mesh-demo
    spec:
      gatewayClassName: built-in-gateway
      listeners:
       - name: proxy
         port: 8080
         protocol: HTTP" | kubectl apply -f -
  2. Validate that the pods are running:

    kubectl wait -n kong-mesh-demo --for=condition=ready pod --selector=app=built-in-gateway --timeout=90s
    kubectl get pods -n kong-mesh-demo

    You should see the following result:

    NAME                               READY   STATUS    RESTARTS   AGE
    built-in-gateway-c759dffc8-w7nlt   1/1     Running   0          9s
    demo-app-84d96db569-6t8kx          2/2     Running   0          106s
    kv-648747567c-qhmxj                2/2     Running   0          106s

    It can take a few minutes for the built-in-gateway Pod to appear. If you only see the demo-app and kv Pods, wait and try again.

  3. Export the gateway’s public IP:

    export PROXY_IP=$(kubectl get svc -n kong-mesh-demo built-in-gateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
    echo $PROXY_IP
  4. Send a request to the gateway to validate that it’s running:

    curl -i $PROXY_IP:8080

    Since we haven’t configured any Routes, you should see the following result:

    HTTP/1.1 404 Not Found
    content-length: 62
    content-type: text/plain
    vary: Accept-Encoding
    date: Tue, 06 Jan 2026 14:36:29 GMT
    server: Kuma Gateway
       
    This is a Kuma MeshGateway. No routes match this MeshGateway!

Create a Route

  1. Create a Route with the MeshHTTPRoute resource and associate it with the built-in gateway:

    echo "apiVersion: gateway.networking.k8s.io/v1
    kind: HTTPRoute
    metadata:
      name: echo
      namespace: kong-mesh-demo
    spec:
      parentRefs:
        - group: gateway.networking.k8s.io
          kind: Gateway
          name: built-in-gateway
          namespace: kong-mesh-demo
      rules:
        - backendRefs:
          - kind: Service
            name: demo-app
            port: 5050
            weight: 1
          matches:
            - path:
                type: PathPrefix
                value: /" | kubectl apply -f -
  2. Send a request to the gateway:

    curl -i $PROXY_IP:8080

    Now the Route exists, but the gateway can’t access the demo app service because of the permissions applied in the demo configuration:

    HTTP/1.1 403 Forbidden
    content-length: 19
    content-type: text/plain
    date: Tue, 06 Jan 2026 14:37:19 GMT
    server: Kuma Gateway
    x-envoy-upstream-service-time: 0
       
    RBAC: access denied%      
  3. Add a MeshTrafficPermission resource to allow traffic to the Service:

    echo "apiVersion: kuma.io/v1alpha1
    kind: MeshTrafficPermission
    metadata:
      namespace: kong-mesh-demo 
      name: demo-app
    spec:
      targetRef:
        kind: Dataplane
        labels:
          app: demo-app
      from:
        - targetRef:
            kind: MeshSubset
            tags: 
              kuma.io/service: built-in-gateway_kong-mesh-demo_svc 
          default:
            action: Allow" | kubectl apply -f -
  4. Send a request to the Route:

    curl -XPOST -i $PROXY_IP:8080/api/counter

    You should get the following result:

    {"counter":1,"zone":""}

Secure your endpoint

With the gateway, we exposed the application to a public endpoint. To secure it, we’ll add TLS to our endpoint.

  1. Create a self-signed certificate:

    openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/CN=$PROXY_IP"
  2. Create a Kubernetes secret containing the certificate and key in the kong-mesh-system namespace:

    echo "apiVersion: v1
    kind: Secret
    metadata:
      name: my-gateway-certificate
      namespace: kong-mesh-system
    type: kubernetes.io/tls
    data:
      tls.crt: "$(cat tls.crt | base64)"
      tls.key: "$(cat tls.key | base64)"" | kubectl apply -f - 
  3. Create a ReferenceGrant to allow the Gateway in kong-mesh-demo to reference the Secret in kong-mesh-system:

    echo "apiVersion: gateway.networking.k8s.io/v1beta1
    kind: ReferenceGrant
    metadata:
      name: allow-gateway-cert
      namespace: kong-mesh-system
    spec:
      from:
        - group: gateway.networking.k8s.io
          kind: Gateway
          namespace: kong-mesh-demo
      to:
        - group: \"\"
          kind: Secret
          name: my-gateway-certificate" | kubectl apply -f -
  4. Enable TLS and add the certificate to the Gateway:

    echo "apiVersion: gateway.networking.k8s.io/v1
    kind: Gateway
    metadata:
      name: built-in-gateway
      namespace: kong-mesh-demo
    spec:
      gatewayClassName: built-in-gateway
      listeners:
        - name: proxy
          port: 8080
          protocol: HTTPS
          tls:
            certificateRefs:
              - name: my-gateway-certificate
                namespace: kong-mesh-system" | kubectl apply -f -
  5. Send a request to the gateway:

    curl -X POST -v --insecure "https://$PROXY_IP:8080/api/counter"

    Since we’re using a self-signed certificate for testing purposes, we need the --insecure flag.

    You should see a successful request with a TLS handshake:

    *   Trying 127.0.0.0:8080...
    * ALPN: curl offers h2,http/1.1
    * TLSv1.3 (OUT), TLS handshake, Client hello (1):
    * SSL Trust: peer verification disabled
    * TLSv1.3 (IN), TLS handshake, Server hello (2):
    * TLSv1.3 (IN), TLS change cipher, Change cipher spec (1):
    * TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
    * TLSv1.3 (IN), TLS handshake, Certificate (11):
    * TLSv1.3 (IN), TLS handshake, CERT verify (15):
    * TLSv1.3 (IN), TLS handshake, Finished (20):
    * TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
    * TLSv1.3 (OUT), TLS handshake, Finished (20):
    * SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 / x25519 / RSASSA-PSS
    * ALPN: server accepted h2
    * Server certificate:
    *   subject: CN=127.0.0.0
    *   start date: May 29 16:22:26 2026 GMT
    *   expire date: May 29 16:22:26 2027 GMT
    *   issuer: CN=127.0.0.0
    *   Certificate level 0: Public key type RSA (2048/112 Bits/secBits), signed using sha256WithRSAEncryption
    * SSL certificate OpenSSL verify result: self-signed certificate (18)
    *  SSL certificate verification failed, continuing anyway!
    * Established connection to 127.0.0.0 (127.0.0.0 port 8080) from 192.168.139.3 port 63650 
    * using HTTP/2
    * [HTTP/2] [1] OPENED stream for https://127.0.0.0:8080/api/counter
    * [HTTP/2] [1] [:method: POST]
    * [HTTP/2] [1] [:scheme: https]
    * [HTTP/2] [1] [:authority: 127.0.0.0:8080]
    * [HTTP/2] [1] [:path: /api/counter]
    * [HTTP/2] [1] [user-agent: curl/8.17.0]
    * [HTTP/2] [1] [accept: */*]
    > POST /api/counter HTTP/2
    > Host: 127.0.0.0:8080
    > User-Agent: curl/8.17.0
    > Accept: */*
    > 
    * Request completely sent off
    * TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
    * TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
    < HTTP/2 200 
    < content-type: application/json; charset=utf-8
    < x-demo-app-version: v1
    < date: Fri, 29 May 2026 16:22:48 GMT
    < content-length: 24
    < x-envoy-upstream-service-time: 15
    < server: Kuma Gateway
    < strict-transport-security: max-age=31536000; includeSubDomains
    < 
    {"counter":2,"zone":""}
    * Connection #0 to host 127.0.0.0:8080 left intact

Help us make these docs great!

Kong Developer docs are open source. If you find these useful and want to make them better, contribute today!