Sitemap
Damindu Sandaruwan Bandara

Damindu Sandaruwan Bandara

DevOps Engineer | 1x GCP Certified | 1x AWS Certified | 1x Azure Certified | Cloud Enthusiastic | BICT (Hons)

Setting Up Envoy Gateway in Kubernetes

10 min readSep 27, 2026

--

Setting Up Envoy Gateway in Kubernetes

Envoy Gateway is a Kubernetes Gateway API implementation built around the Envoy proxy. It provides a modern way to expose Kubernetes applications through HTTP/HTTPS routing, while keeping application Services internal to the cluster.

This guide walks through a complete setup:

Client → Envoy Gateway → Gateway → HTTPRoute → Kubernetes Service → Application Pod

The examples are intentionally simple so that you can use them as a starting point for a development or test Kubernetes cluster.

Press enter or click to view image in full size

1. What is Envoy Gateway?

Traditionally, Kubernetes applications are exposed using resources such as Ingress:

Internet
|
v
Ingress Controller
|
v
Kubernetes Service
|
v
Application Pod

Envoy Gateway uses the Kubernetes Gateway API, which separates infrastructure configuration from application routing.

The architecture becomes:

Kubernetes Cluster
┌─────────────────────────────────────────────────────┐
│ │
│ Client │
│ │ │
│ │ HTTP/HTTPS │
│ ▼ │
│ ┌───────────────────────┐ │
│ │ Envoy Gateway │ │
│ │ / Envoy Proxy │ │
│ └───────────┬───────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ Gateway │ │
│ └──────┬───────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ HTTPRoute │ │
│ └──────┬───────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ Kubernetes │ │
│ │ Service │ │
│ └──────┬───────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ Application │ │
│ │ Pod │ │
│ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────┘

The Gateway API defines resources such as GatewayClass, Gateway, and HTTPRoute. HTTPRoute is a GA resource in the Gateway API and is used to define HTTP routing from a Gateway to Kubernetes Services. (Gateway API)

2. Main Envoy Gateway Components

There are several important Kubernetes resources involved.

GatewayClass

Defines which controller manages the Gateway.

kind: GatewayClass

For Envoy Gateway, this normally points to the Envoy Gateway controller.

Gateway

Defines the network entry point.

For example:

kind: Gateway

A Gateway can define:

  • HTTP listener
  • HTTPS listener
  • TLS configuration
  • allowed namespaces
  • ports
  • hostnames

HTTPRoute

Defines how HTTP traffic should be routed.

For example:

example.com/api
|
v
HTTPRoute
|
v
backend-service:8080

An HTTPRoute references a Gateway using parentRefs and specifies backend Services using backendRefs. (Gateway API)

3. Prerequisites

You need:

  • A running Kubernetes cluster
  • kubectl
  • Helm
  • Network access to install Envoy Gateway
  • A LoadBalancer implementation if you want an externally accessible Gateway

For a cloud Kubernetes cluster, such as EKS, GKE or AKS, a LoadBalancer implementation is normally available.

For bare-metal or local Kubernetes, you may need something such as MetalLB. Envoy’s documentation specifically recommends having a LoadBalancer implementation when the cluster does not already provide one. (Envoy Gateway)

Check your cluster:

kubectl get nodes

Example:

NAME              STATUS   ROLES           AGE
master-01 Ready control-plane 20d
worker-01 Ready <none> 20d
worker-02 Ready <none> 20d

Check Helm:

helm version

Check Kubernetes:

kubectl version

4. Install Envoy Gateway

The Envoy Gateway documentation provides Helm-based installation. The current quickstart documentation uses the Envoy Gateway Helm OCI chart. (Envoy Gateway)

Create the namespace and install Envoy Gateway:

helm install eg \
oci://docker.io/envoyproxy/gateway-helm \
--version v1.9.1 \
-n envoy-gateway-system \
--create-namespace

For production environments, pin a tested Envoy Gateway version rather than blindly using latest.

Check the installation:

kubectl get pods -n envoy-gateway-system

You should see something similar to:

NAME                             READY   STATUS    RESTARTS   AGE
envoy-gateway-xxxxxxxxxx-xxxxx 1/1 Running 0 1m

Wait until Envoy Gateway is ready:

kubectl wait --timeout=5m -n envoy-gateway-system \
deployment/envoy-gateway --for=condition=Available

This is also the readiness check recommended by the Envoy Gateway quickstart. (Envoy Gateway)

5. Verify Gateway API Resources

Check the Gateway API CRDs:

kubectl get crd | grep gateway

You should see resources such as:

gatewayclasses.gateway.networking.k8s.io
gateways.gateway.networking.k8s.io
httproutes.gateway.networking.k8s.io
referencegrants.gateway.networking.k8s.io

Check GatewayClasses:

kubectl get gatewayclass

Expected:

NAME   CONTROLLER
eg gateway.envoyproxy.io/gatewayclass-controller

The exact controller output can vary with the Envoy Gateway version.

6. Create a Sample Application

Now let’s create a very simple web application.

We’ll use the ealen/echo-server image because it makes it easy to see HTTP request information.

Create:

mkdir envoy-demo
cd envoy-demo

Create application.yaml:

apiVersion: v1
kind: Namespace
metadata:
name: envoy-demo
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: echo-app
namespace: envoy-demo
spec:
replicas: 2
selector:
matchLabels:
app: echo-app
template:
metadata:
labels:
app: echo-app
spec:
containers:
- name: echo-app
image: ealen/echo-server:latest
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: echo-service
namespace: envoy-demo
spec:
selector:
app: echo-app
ports:
- name: http
port: 80
targetPort: 80

Apply it:

kubectl apply -f application.yaml

Check the pods:

kubectl get pods -n envoy-demo

Expected:

NAME                         READY   STATUS    RESTARTS   AGE
echo-app-xxxxxxxxxx-xxxxx 1/1 Running 0 30s
echo-app-xxxxxxxxxx-yyyyy 1/1 Running 0 30s

Check the Service:

kubectl get svc -n envoy-demo

Expected:

NAME           TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)
echo-service ClusterIP 10.96.100.100 <none> 80/TCP

Notice that the application Service is only ClusterIP.

We don’t need to expose the application itself as a NodePort or LoadBalancer.

Envoy Gateway will provide the external access.

7. Create the Gateway

Now create the actual Gateway.

Create gateway.yaml:

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: envoy-gateway
namespace: envoy-demo
spec:
gatewayClassName: eg
  listeners:
- name: http
protocol: HTTP
port: 80

Apply:

kubectl apply -f gateway.yaml

Check:

kubectl get gateway -n envoy-demo

Example:

NAME             CLASS   ADDRESS         PROGRAMMED   AGE
envoy-gateway eg 192.168.1.100 True 30s

The ADDRESS is particularly important.

It represents the address through which clients can reach the Gateway.

8. Create an HTTPRoute

Now we need to tell Envoy where incoming requests should go.

Create httproute.yaml:

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: echo-route
namespace: envoy-demo
spec:
parentRefs:
- name: envoy-gateway
  rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: echo-service
port: 80

Apply:

kubectl apply -f httproute.yaml

Check:

kubectl get httproute -n envoy-demo

Expected:

NAME         HOSTNAMES   AGE
echo-route 30s

Describe it:

kubectl describe httproute echo-route -n envoy-demo

Look for:

Accepted: True

and:

ResolvedRefs: True

These conditions are useful when troubleshooting route configuration.

9. Understanding the Complete Configuration

At this point we have:

HTTP request
|
v
┌───────────────┐
│ Envoy Gateway │
│ :80 │
└───────┬───────┘
|
v
┌───────────────┐
│ HTTPRoute │
│ / │
└───────┬───────┘
|
v
┌───────────────┐
│ echo-service │
│ :80 │
└───────┬───────┘
|
┌──────┴──────┐
│ │
v v
echo-pod-1 echo-pod-2

The important relationship is:

Gateway
|
+---- HTTPRoute
|
+---- Service
|
+---- Pods

This separation is one of the major advantages of Gateway API.

10. Check the Envoy Service

Envoy Gateway creates the required Envoy proxy infrastructure when the Gateway is programmed.

Check Services:

kubectl get svc -A

Look for something similar to:

envoy-gateway-system   envoy-envoy-gateway-xxxxx

You can also search:

kubectl get svc -A | grep envoy

For a LoadBalancer-based setup, you may see:

NAME                    TYPE           CLUSTER-IP      EXTERNAL-IP
envoy-xxxxx LoadBalancer 10.96.10.20 192.168.1.100

On a cloud provider it could instead be:

EXTERNAL-IP
a123456789.elb.amazonaws.com

11. Test the Application

Get the Gateway address:

kubectl get gateway envoy-gateway -n envoy-demo

For example:

NAME            CLASS   ADDRESS        PROGRAMMED
envoy-gateway eg 192.168.1.100 True

Test:

curl http://192.168.1.100/

The request should reach:

Envoy
↓
HTTPRoute
↓
echo-service
↓
echo-app

Because we’re using an echo server, the response should contain information about the HTTP request.

12. Host-Based Routing

One of the useful features of Gateway API is hostname-based routing.

Get Damindu Sandaruwan Bandara’s stories in your inbox

Join Medium for free to get updates from this writer.

For example:

api.example.com
|
v
api-service
web.example.com
|
v
web-service

Modify the Gateway:

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: envoy-gateway
namespace: envoy-demo
spec:
gatewayClassName: eg
  listeners:
- name: http
protocol: HTTP
port: 80
hostname: "*.example.com"

Then create:

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: echo-route
namespace: envoy-demo
spec:
parentRefs:
- name: envoy-gateway
  hostnames:
- "echo.example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: echo-service
port: 80

Now the route is associated with:

echo.example.com

Test using:

curl \
--header "Host: echo.example.com" \
http://192.168.1.100/

The Gateway API supports hostname matching before evaluating the HTTPRoute rules. (Envoy Gateway)

13. Path-Based Routing

You can also route different paths to different applications.

For example:

example.com/api
|
v
api-service
example.com/web
|
v
web-service

Example:

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: application-route
namespace: envoy-demo
spec:
parentRefs:
- name: envoy-gateway
  rules:    - matches:
- path:
type: PathPrefix
value: /api
backendRefs:
- name: api-service
port: 8080
- matches:
- path:
type: PathPrefix
value: /web
backendRefs:
- name: web-service
port: 8080

This allows one Gateway to expose multiple applications.

The Gateway API HTTP routing guide uses the same basic concept: HTTPRoute rules can match request paths and forward traffic to different Kubernetes Services. (Gateway API)

14. Traffic Splitting

Gateway API can also distribute traffic between backend versions.

For example:

HTTP
|
v
Envoy Gateway
|
HTTPRoute
|
┌───────┴────────┐
| |
v v
v1 Service v2 Service
90% 10%

Example:

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: echo-route
namespace: envoy-demo
spec:
parentRefs:
- name: envoy-gateway
  rules:
- backendRefs:
- name: echo-v1
port: 80
weight: 90
- name: echo-v2
port: 80
weight: 10

This is useful for:

  • Canary deployments
  • Blue/green deployments
  • Application migrations
  • A/B testing

Gateway API supports weighted backend references for this type of traffic splitting. (Envoy Gateway)

15. HTTPS / TLS

For production applications, HTTP should normally be replaced or supplemented with HTTPS.

The architecture becomes:

Client
|
| HTTPS :443
|
v
Envoy Gateway
|
| HTTP
|
v
Application Service

A Gateway can have an HTTPS listener:

listeners:
- name: https
protocol: HTTPS
port: 443
    tls:
mode: Terminate
certificateRefs:
- name: example-tls

The TLS Secret might look like:

kubectl create secret tls example-tls \
--cert=tls.crt \
--key=tls.key \
-n envoy-demo

Then the Gateway terminates TLS and forwards normal HTTP traffic to the backend.

For production, certificates can be managed automatically using tools such as cert-manager.

16. Cross-Namespace Routing

One important Gateway API feature is the separation between infrastructure and applications.

For example:

envoy-gateway-system
|
|
Envoy Gateway
platform namespace
|
Gateway
application namespace
|
HTTPRoute
|
Service

An HTTPRoute doesn’t automatically get permission to reference resources across namespaces. Gateway API provides mechanisms such as allowedRoutes and ReferenceGrant to control these relationships.

For example:

listeners:
- name: http
protocol: HTTP
port: 80
    allowedRoutes:
namespaces:
from: All

This means HTTPRoutes from other namespaces can attach to the listener.

A more restrictive configuration could be:

allowedRoutes:
namespaces:
from: Same

This means only Routes from the same namespace can attach.

This is particularly important in multi-team Kubernetes clusters.

17. Useful Troubleshooting Commands

If the Gateway isn’t working, start with:

kubectl get gatewayclass

Then:

kubectl get gateway -A

Then:

kubectl get httproute -A

Check Gateway details:

kubectl describe gateway envoy-gateway -n envoy-demo

Check the HTTPRoute:

kubectl describe httproute echo-route -n envoy-demo

Check Envoy Gateway:

kubectl get pods -n envoy-gateway-system

Check logs:

kubectl logs \
-n envoy-gateway-system \
deployment/envoy-gateway

Find Envoy Services:

kubectl get svc -A | grep envoy

Check endpoints:

kubectl get endpoints -n envoy-demo

Or:

kubectl get endpointslices -n envoy-demo

18. Common Problems

Gateway has no ADDRESS

Check:

kubectl get gateway -n envoy-demo

If you see:

ADDRESS   <none>

check the generated Envoy Service:

kubectl get svc -A | grep envoy

If the Service is:

ClusterIP

or has no external address, your Kubernetes cluster may not have a LoadBalancer implementation.

On bare-metal clusters, installing MetalLB is one possible solution, as recommended in the Envoy Gateway documentation. (Envoy Gateway)

HTTPRoute shows Accepted: False

Run:

kubectl describe httproute echo-route -n envoy-demo

Look at:

Status:
Parents:

Typical causes include:

  • Wrong Gateway name
  • Wrong namespace
  • Gateway doesn’t allow the Route namespace
  • Invalid backend Service
  • Invalid backend port
  • Gateway listener mismatch

404 from Envoy

A 404 often means the request reached Envoy but did not match the configured route.

Check:

kubectl describe httproute echo-route -n envoy-demo

If using a hostname:

hostnames:
- echo.example.com

test with:

curl \
-H "Host: echo.example.com" \
http://<GATEWAY-IP>/

Without the correct Host header, the HTTPRoute may not match.

503 from Envoy

A 503 generally indicates that Envoy cannot successfully reach the backend.

Check:

kubectl get svc -n envoy-demo

Then:

kubectl get endpoints -n envoy-demo

You should see pod IPs associated with:

echo-service

Also verify:

kubectl get pods -n envoy-demo

and:

kubectl describe pod <pod-name> -n envoy-demo

19. Complete Example

For a simple development environment, you can keep the configuration in three files.

application.yaml

apiVersion: v1
kind: Namespace
metadata:
name: envoy-demo
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: echo-app
namespace: envoy-demo
spec:
replicas: 2
selector:
matchLabels:
app: echo-app
template:
metadata:
labels:
app: echo-app
spec:
containers:
- name: echo-app
image: ealen/echo-server:latest
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: echo-service
namespace: envoy-demo
spec:
selector:
app: echo-app
ports:
- port: 80
targetPort: 80

gateway.yaml

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: envoy-gateway
namespace: envoy-demo
spec:
gatewayClassName: eg
  listeners:
- name: http
protocol: HTTP
port: 80

httproute.yaml

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: echo-route
namespace: envoy-demo
spec:
parentRefs:
- name: envoy-gateway
  rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: echo-service
port: 80

Deploy everything:

kubectl apply -f application.yaml
kubectl apply -f gateway.yaml
kubectl apply -f httproute.yaml

Then:

kubectl get gateway -n envoy-demo

Get the Gateway address and test:

curl http://<GATEWAY-ADDRESS>/

20. Final Architecture

After completing the setup, your Kubernetes environment looks like this:

Internet
|
| HTTP / HTTPS
v
┌──────────────────────┐
│ LoadBalancer │
└──────────┬───────────┘
|
v
┌──────────────────────┐
│ Envoy Proxy │
│ │
│ Managed by Envoy │
│ Gateway │
└──────────┬───────────┘
|
v
┌──────────────────────┐
│ Gateway │
│ │
│ HTTP :80 │
│ HTTPS :443 │
└──────────┬───────────┘
|
v
┌──────────────────────┐
│ HTTPRoute │
│ │
│ / │
│ /api │
│ /web │
└──────────┬───────────┘
|
┌──────────────┼──────────────┐
| | |
v v v
api-service web-service other-service
| | |
v v v
Pods Pods Pods

Conclusion

Envoy Gateway provides a clean separation between platform networking and application routing. Instead of exposing every application with its own LoadBalancer or relying on a large collection of Ingress rules, a platform team can provide a shared Gateway while application teams define their own HTTPRoute resources.

The basic workflow is:

1. Install Envoy Gateway
↓
2. GatewayClass
↓
3. Create Gateway
↓
4. Deploy application
↓
5. Create Service
↓
6. Create HTTPRoute
↓
7. Test through Gateway

For production, the next logical steps are HTTPS/TLS, cross-namespace routing, authentication, rate limiting, observability, retries/timeouts, and canary traffic splitting. Envoy Gateway’s documentation also provides dedicated tasks for secure gateways, traffic management, rate limiting, and gRPC routing. (Envoy Gateway)

Official references: Envoy Gateway Quickstart · Gateway API HTTPRoute documentation · Gateway API HTTP routing guide

--

--

Written by Damindu Sandaruwan Bandara

DevOps Engineer | 1x GCP Certified | 1x AWS Certified | 1x Azure Certified | Cloud Enthusiastic | BICT (Hons)