# Move off the retired ingress-nginx to Gateway API

> Ingress NGINX was retired in March 2026. Recreate the shop's Ingress rules, rewrite included, as an HTTPRoute on Envoy Gateway, then remove ingress-nginx.

Level: Advanced · Topic: Networking · About 20 minutes
Try it in the browser: https://k8wiz.com/playground/migrate-from-ingress-nginx-to-gateway-api

The playground is a simulated Kubernetes cluster that runs in the browser. kubectl output and errors follow the real ones.

## The situation

ingress-nginx publishes the shop: the shop-api Ingress sends /api to the api Service with a `rewrite-target`, and shop-web sends everything else to web. Ingress NGINX was retired on March 24, 2026. It keeps working, but gets no more releases or security fixes.

The platform team runs Envoy Gateway with a Gateway called public in the infra namespace. Recreate the routes as an HTTPRoute, test them against the Gateway before moving traffic, then remove the Ingresses and ingress-nginx.

## Objectives

1. Find the ingress-nginx controller (`kubectl get pods -A -l app.kubernetes.io/name=ingress-nginx`)
2. Serve /api and / for shop.example.com from the Gateway, rewrite included
3. Test the Gateway before moving traffic (`curl --resolve shop.example.com:80:<gateway address> http://shop.example.com/api/orders`)
4. Remove the Ingresses and ingress-nginx, with the shop still answering

## Hints

1. `kubectl get pods -A -l app.kubernetes.io/name=ingress-nginx` finds the controller. `kubectl get ingress -o yaml` shows the rules and the annotations only ingress-nginx reads: `use-regex` and `rewrite-target: /$2`, which turns /api/orders into /orders.
2. In Gateway API the rewrite is part of the rule: match `PathPrefix` `/api` and add a `URLRewrite` filter with `ReplacePrefixMatch: /`. httproute.yaml in the Editor has the start. Upstream, `ingress2gateway` converts Ingresses like these for you.
3. Get the Gateway's address with `kubectl get gateway public -n infra`, then test without touching DNS: `curl --resolve shop.example.com:80:<address> http://shop.example.com/api/orders`.
4. Then `kubectl delete ingress shop-api shop-web` and `kubectl delete namespace ingress-nginx`. shop.example.com then resolves to the Gateway.

## Solution

```bash
kubectl get pods -A -l app.kubernetes.io/name=ingress-nginx
```
```bash
kubectl get ingress shop-api -o yaml | grep -A3 annotations
```
```bash
cat <<EOF | kubectl apply -f -
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: shop
  namespace: shop
spec:
  parentRefs:
  - name: public
    namespace: infra
  hostnames:
  - shop.example.com
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /api
    filters:
    - type: URLRewrite
      urlRewrite:
        path:
          type: ReplacePrefixMatch
          replacePrefixMatch: /
    backendRefs:
    - name: api
      port: 8080
  - backendRefs:
    - name: web
      port: 80
EOF
```
```bash
sleep 3
```
```bash
GW=$(kubectl get gateway public -n infra -o jsonpath='{.status.addresses[0].value}'); curl -s --resolve shop.example.com:80:$GW http://shop.example.com/api/orders
```
```bash
kubectl delete ingress shop-api shop-web
```
```bash
kubectl delete namespace ingress-nginx
```
```bash
sleep 5
```
```bash
curl -s http://shop.example.com/api/orders
```

## What it teaches

- Ingress NGINX was retired on March 24, 2026. Existing installs keep running with no more releases or security fixes, so plan the move rather than waiting for a CVE.
- Each Ingress controller reads its own annotations. Gateway API puts the same features in the spec: a `rewrite-target` becomes a `URLRewrite` filter, a host and path become `hostnames` and `matches`.
- Test the new route against the Gateway's address with `curl --resolve` before moving DNS, then remove the old Ingresses so two controllers don't serve the same host.
- `ingress2gateway` converts ingress-nginx Ingresses to Gateway API resources. Review what it writes: not every annotation has an equivalent.
