Michael van Tricht

Software Engineer

Migrating from Ingress NGINX to Envoy Gateway

I have been using Ingress NGINX since early 2021 and have always enjoyed working with it. With it being retired and only receiving updates until March 2026, I decided it was a good time to explore alternatives. This is my short journey of searching for a replacement.

My current ingress-nginx setup is fairly straightforward. A set of standard ingress rules and a wildcard SSL certificate managed by cert-manager. Nothing too unusual except that the wildcard certificate is a default certificate and none of the ingresses reference a certificate.

My initial goal was to keep using the Kubernetes ingress API and to only switch out the controller underneath. I evaluated the following replacements:

Since none of these were a great fit, I decided to learn the “new” Kubernetes Gateway API which is likely to replace the ingress API in the long term. One of the implementations of the Gateway API is Envoy Gateway which uses Envoy, a high performance C++ proxy, though there are many others.

A key difference from ingress is that the Gateway API splits responsibilities into multiple manifest files, giving you more flexibility but also more YAML. In a nutshell, my old ingress manifest file:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    nginx.ingress.kubernetes.io/allowlist-source-range: 192.168.1.0/24
    nginx.ingress.kubernetes.io/from-to-www-redirect: "true"
  name: blog
spec:
  ingressClassName: nginx
  rules:
  - host: tricht.eu
    http:
      paths:
      - backend:
          service:
            name: blog
            port:
              number: 80
        path: /
        pathType: Prefix
  tls:
  - hosts:
    - www.tricht.eu
    - tricht.eu

is replaced with an HTTPRoute (other routes such as GRPCRoute, UDPRoute and TCPRoute are available):

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: blog
  labels:
    private: "true"
spec:
  parentRefs:
    - name: envoy-gateway
      namespace: envoy-gateway
  hostnames:
    - "tricht.eu"
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /
      backendRefs:
        - name: blog
          port: 80

and a Gateway:

apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
  name: envoy-gateway-class
spec:
  controllerName: gateway.envoyproxy.io/gatewayclass-controller
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: envoy-gateway
  namespace: envoy-gateway
spec:
  gatewayClassName: envoy-gateway-class
  infrastructure:
    parametersRef:
      group: gateway.envoyproxy.io
      kind: EnvoyProxy
      name: envoy-proxy
  listeners:
    - name: https
      hostname: "tricht.eu"
      port: 443
      protocol: HTTPS
      tls:
        mode: Terminate
        certificateRefs:
          - group: ""
            kind: Secret
            name: wildcard-tricht-eu
    - name: https-subdomain
      hostname: "*.tricht.eu"
      port: 443
      protocol: HTTPS
      tls:
        mode: Terminate
        certificateRefs:
          - group: ""
            kind: Secret
            name: wildcard-tricht-eu
    - name: http
      hostname: "tricht.eu"
      port: 80
      protocol: HTTP
    - name: http-subdomain
      hostname: "*.tricht.eu"
      port: 80
      protocol: HTTP

and to redirect from www to non-www (the nginx.ingress.kubernetes.io/from-to-www-redirect annotation):

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: www-redirect
  namespace: envoy-gateway
spec:
  parentRefs:
    - name: envoy-gateway
  hostnames:
    - "www.tricht.eu"
  rules:
    - filters:
        - type: RequestRedirect
          requestRedirect:
            scheme: https
            hostname: "tricht.eu"
            statusCode: 301

and to use an IP allowlist (the nginx.ingress.kubernetes.io/allowlist-source-range annotation):

apiVersion: gateway.envoyproxy.io/v1alpha1
kind: SecurityPolicy
metadata:
  name: authorization-client-ip
spec:
  targetSelectors:
  - kind: HTTPRoute
    matchLabels:
      private: "true"
  authorization:
    defaultAction: Deny
    rules:
    - action: Allow
      principal:
        clientCIDRs:
        - 192.168.1.0/24

As you can see the Gateway API is highly extensible. Gateway implementations such as Envoy Gateway can provide additional CRDs like SecurityPolicy on top of the standard objects to allow further customization. And thankfully there is the ingress2gateway command-line tool to help you migrate!