Set up a built-in gateway with Kong Mesh

Uses: Kong Mesh
Related Documentation
Minimum Version
Kong Mesh - 2.6
TL;DR

Create a MeshGatewayInstance and a MeshGateway to configure the built-in gateway, then create a MeshHTTPRoute and allow traffic to the gateway with a MeshTrafficPermission. To secure your endpoint, generate a certificate and add it to the MeshGateway.

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 -
    

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 add a built-in gateway in front of the demo-app service and expose it publicly.

Configure the gateway

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

    echo "apiVersion: kuma.io/v1alpha1
    kind: MeshGatewayInstance
    metadata:
      name: built-in-gateway
      namespace: kong-mesh-demo
    spec:
      replicas: 1
      serviceType: LoadBalancer" | kubectl apply -f -
    
  2. Use the MeshGateway resource to define an HTTP listener on port 8080:

    echo "apiVersion: kuma.io/v1alpha1
    kind: MeshGateway
    mesh: default
    metadata:
      name: built-in-gateway
      namespace: kong-mesh-demo
    spec:
      conf:
        listeners:
          - port: 8080
            protocol: HTTP
      selectors:
        - match:
            kuma.io/service: built-in-gateway_kong-mesh-demo_svc" | kubectl apply -f -
    
  3. Validate that the pods are running:

    kubectl get pods -n kong-mesh-demo
    

    You should see the following result:

    NAME                              READY   STATUS    RESTARTS   AGE
    built-in-gateway-c49549bf-4z66n   1/1     Running   0          9s
    demo-app-84d96db569-dqjv6         2/2     Running   0          110s
    kv-648747567c-4cbkh               2/2     Running   0          110s
    
  4. 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
    
  5. 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: kuma.io/v1alpha1
    kind: MeshHTTPRoute
    metadata:
      name: demo-app-built-in-gateway
      namespace: kong-mesh-system
    spec:
      targetRef:
        kind: MeshGateway
        name: built-in-gateway
      to:
        - targetRef:
            kind: Mesh
          rules:
            - default:
                backendRefs:
                  - kind: MeshService
                    name: demo-app
                    namespace: kong-mesh-demo
                    port: 5050
              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:
    echo "apiVersion: v1
    kind: Secret
    metadata:
      name: my-gateway-certificate
      namespace: kong-mesh-system 
      labels:
        kuma.io/mesh: default
    data:
      value: "$(cat tls.key tls.crt | base64)"
    type: system.kuma.io/secret" | kubectl apply -f - 
    
  3. Update the gateway to use the certificate:

    echo "apiVersion: kuma.io/v1alpha1
    kind: MeshGateway
    mesh: default
    metadata:
      name: built-in-gateway
    spec:
      selectors:
        - match:
            kuma.io/service: built-in-gateway_kong-mesh-demo_svc
      conf:
        listeners:
          - port: 8080
            protocol: HTTPS
            tls:
              mode: TERMINATE
              certificates:
                - secret: my-gateway-certificate
            tags:
              port: http-8080" | kubectl apply -f -
    
  4. 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...
    * Connected to 127.0.0.0 (127.0.0.0) port 8080
    * ALPN: curl offers h2,http/1.1
    * (304) (OUT), TLS handshake, Client hello (1):
    * (304) (IN), TLS handshake, Server hello (2):
    * (304) (IN), TLS handshake, Unknown (8):
    * (304) (IN), TLS handshake, Certificate (11):
    * (304) (IN), TLS handshake, CERT verify (15):
    * (304) (IN), TLS handshake, Finished (20):
    * (304) (OUT), TLS handshake, Finished (20):
    * SSL connection using TLSv1.3 / AEAD-CHACHA20-POLY1305-SHA256 / [blank] / UNDEF
    * ALPN: server accepted h2
    * Server certificate:
    *  subject: CN=127.0.0.0
    *  start date: Jan  6 14:38:19 2026 GMT
    *  expire date: Jan  6 14:38:19 2027 GMT
    *  issuer: CN=127.0.0.0
    *  SSL certificate verify result: self signed certificate (18), continuing anyway.
    * 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.7.1]
    * [HTTP/2] [1] [accept: */*]
    > POST /api/counter HTTP/2
    > Host: 127.0.0.0:8080
    > User-Agent: curl/8.7.1
    > Accept: */*
    > 
    * Request completely sent off
    < HTTP/2 200 
    < content-type: application/json; charset=utf-8
    < x-demo-app-version: v1
    < date: Tue, 06 Jan 2026 15:01:35 GMT
    < content-length: 24
    < x-envoy-upstream-service-time: 25
    < server: Kuma Gateway
    < strict-transport-security: max-age=31536000; includeSubDomains
    < 
    {"counter":2,"zone":""}
    
Something wrong?

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!