DEV Community

Alister Baroi for Tigera Inc

Posted on • Originally published at tigera.io on

Migrating from Ingress NGINX to Calico Ingress Gateway: A Step-by-Step Guide

From Ingress NGINX to Calico Ingress Gateway

In our previous post, we addressed the most common questions platform teams are asking as they prepare for the retirement of the NGINX Ingress Controller. With the March 2026 deadline fast approaching, this guide provides a hands-on, step-by-step walkthrough for migrating to the Kubernetes Gateway API using Calico Ingress Gateway. You will learn how to translate NGINX annotations into HTTPRoute rules, run both models side by side, and safely cut over live traffic.

A Brief History

The announced retirement of the NGINX Ingress Controller has created a forced migration path for the many teams that relied on it as the industry standard. While the Ingress API is not yet officially deprecated, the Kubernetes SIG Network has designated the Gateway API as its official successor. Legacy Ingress will no longer receive enhancements and exists primarily for backward compatibility.

Why the Industry is Standardizing on Gateway API

While the Ingress API served the community for years, it reached a functional ceiling. Calico Ingress Gateway implements the Gateway API to provide:

  • Role-Oriented Design: Clear separation between the infrastructure (managed by SREs) and routing logic (managed by Developers).
  • Native Expressiveness: Features like URL rewrites and header manipulation are first-class citizens in the spec, not vendor-specific annotations.
  • Unified Security: Ingress traffic is finally governed by the same Calico Network Policies that secure your east-west traffic.
Feature Comparison Calico Ingress Gateway Ingress NGINX
Foundation 100% based on K8s Gateway API (Vendor-agnostic) Built on the legacy Kubernetes Ingress API specification (no longer being enhanced)
Lifecycle Enterprise support / CVE protection No support/updates after March 2026
Traffic Control Wide range of native features Less features; reliant on custom annotations
RBAC Extremely granular / flexible Less flexible / granular
Installation Tigera Operator More difficult to install/maintain
Unified Platform Part of a single solution for networking and security A separate product; not part of a unified platform

Prerequisites & Preparation

Before you begin the migration, ensure your environment meets these requirements:

  • Calico version
    • Calico Open Source 3.30+
    • Calico Cloud
    • Calico Enterprise 3.21+
  • Gateway API CRDs
    • Installed automatically by the Tigera Operator
  • Ingress inventory
    • Audit existing Ingress resources and annotations (timeouts, rewrites, headers)
  • TLS certificates
    • TLS secrets must exist in each gateway namespace

Create TLS Secrets (Required for HTTPS)

kubectl create secret tls gateway-tls-secret -n your-gateway-namespace --cert=path/to/tls.crt --key=path/to/tls.key
Enter fullscreen mode Exit fullscreen mode

Migration Guide: Table of Contents

Migration Walkthrough: NGINX to Gateway API

Phase 1: Enabling Calico Ingress Gateway

Setting up the Calico Ingress Gateway involves two main steps: creating the Gateway configuration and deploying the actual Gateway instance.

1.1. Enable the Gateway API Resource

First, you need to enable Calico Ingress Gateway capabilities. Calico implements Gateway API by integrating with a hardened Envoy Gateway image that is based in Envoy Gateway 1.3.2.

kubectl create -f - <<EOF
apiVersion: operator.tigera.io/v1
kind: GatewayAPI
metadata:
 name: default
EOF
Enter fullscreen mode Exit fullscreen mode

1.2. Verify Gateway API Resource Availability

Verify that the Gateway API resources and an Envoy Gateway implementation are available.

# Make sure Gateway API resources have been installed
kubectl api-resources | grep gateway.networking.k8s.io
Enter fullscreen mode Exit fullscreen mode

Output:

backendtlspolicies btlspolicy gateway.networking.k8s.io/v1alpha3 true BackendTLSPolicy
gatewayclasses gc gateway.networking.k8s.io/v1 false GatewayClass
gateways gtw gateway.networking.k8s.io/v1 true Gateway
grpcroutes gateway.networking.k8s.io/v1 true GRPCRoute
httproutes gateway.networking.k8s.io/v1 true HTTPRoute
referencegrants refgrant gateway.networking.k8s.io/v1beta1 true ReferenceGrant
tcproutes gateway.networking.k8s.io/v1alpha2 true TCPRoute
tlsroutes gateway.networking.k8s.io/v1alpha2 true TLSRoute
udproutes gateway.networking.k8s.io/v1alpha2. true UDPRoute
Enter fullscreen mode Exit fullscreen mode

You should see a list of resources including gatewayclasses, gateways, and httproutes.

Next, confirm the Envoy Gateway controller is active:

# There should be an Envoy Gateway implementation
kubectl get gatewayclass -o=jsonpath='{.items[0].spec}' | jq
Enter fullscreen mode Exit fullscreen mode

Output:

{
  "controllerName": "gateway.envoyproxy.io/gatewayclass-controller",
  "parametersRef": {
    "group": "gateway.envoyproxy.io",
    "kind": "EnvoyProxy",
    "name": "tigera-gateway-class",
    "namespace": "tigera-gateway"
  }
}
Enter fullscreen mode Exit fullscreen mode

1.3. Deploy and Configure the Gateway

Now, create the Gateway resource. Calico will automatically provision the underlying pods and a LoadBalancer service to handle incoming traffic. In this example we will deploy the Gateway with an HTTPS listener included because security should not be optional. Ideally you will want to automate certificate management.

In this example we will deploy the Gateway with an HTTPS listener included because security should not be optional. Ideally you will want to automate certificate management. An example of how to do this with Let’s Encrypt can be found at the end of this document.

kubectl create -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
 name: your-calico-ingress-gateway
 Namespace: your-gateway-namespace
spec:
 gatewayClassName: tigera-gateway-class
 listeners:
   - name: http
     protocol: HTTP
     port: 80
   - name: https
     protocol: HTTPS
     port: 443
     hostname: your-domain.com
     tls:
       mode: Terminate
       certificateRefs:
       - kind: Secret
         name: gateway-tls-secret
EOF
Enter fullscreen mode Exit fullscreen mode
Optional: Cross-Namespace Routing with ReferenceGrant

If you plan to have your gateway in one namespace and the services it routes traffic to in another you will need to create a ReferenceGrant. This is a very flexible and secure way to do cross-namespace routing.

Pro Tip: Optional but recommended for cross-namespace routing.

kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
  name: your-reference-grant
  namespace: your-workload-namespace
spec:
  from:
  - group: gateway.networking.k8s.io
    kind: HTTPRoute
    namespace: your-gateway-namespace
  to:
  - group: ""
    kind: Service
Enter fullscreen mode Exit fullscreen mode

1.4. Verify the Setup

Check that the Gateway has been assigned a public IP address (or hostname if you are on a cloud provider like AWS/Azure)

kubectl get svc -n tigera-gateway
Enter fullscreen mode Exit fullscreen mode

Output:

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
envoy-default-eg-e41e7b31 LoadBalancer 10.108.183.215 192.168.10.120 80:30161/TCP 4d20h
Enter fullscreen mode Exit fullscreen mode

Phase 2: Converting Ingress Resources to Gateway API

The next step is to convert any Ingress resources to the new HTTPRoute resources that are a part of Kubernetes Gateway API.

When moving from Ingress NGINX Controller to Calico Ingress Gateway, the biggest challenge is moving away from annotations and converting them to HTTPRoute rules. In the old Ingress model, you had to “hint” at what you wanted using metadata. In the Gateway API, these functions are built directly into the HTTPRoute spec.

Common Ingress NGINX Controller Annotation Mapping
Feature
---
Path Rewrite
Redirects
Request Headers
Response Headers
App Root
Security

See the full list of Ingress NGINX Controller annotations for description of each annotation to help you find the best replacement HTTPRoute rule.


Examples of Ingress to HTTPRoute conversion


Example 1: Basic Path-Based Routing

In this scenario, we are routing traffic to a “backend” service based on the URL path.

Before: NGINX Ingress (Legacy Configuration)

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: basic-ingress
spec:
  rules:
  - http:
      paths:
      - path: /your-api
        pathType: Prefix
        backend:
          service:
            name: api-service
            port:
              number: 80
Enter fullscreen mode Exit fullscreen mode

After: Calico HTTPRoute (Gateway API)

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: api-route
spec:
  parentRefs:
  - name: your-calico-ingress-gateway # References the Gateway we created in Section 3
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /your-api
    backendRefs:
    - name: api-service
      port: 80

Enter fullscreen mode Exit fullscreen mode

Example 2: URL Rewriting & Header Manipulation

Use case: Strip a path prefix and inject a request header.

This is a more complex example where we need to strip a prefix before the request hits the application and add a custom header. In the NGINX model, this required specific annotations and snippets; in Gateway API, these are handled by native filters.

Before: NGINX Ingress (Legacy Configuration)

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: rewrite-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /$2
    nginx.ingress.kubernetes.io/configuration-snippet: |
      more_set_headers "X-Source: Calico-Migration";
spec:
  rules:
  - http:
      paths:
      - path: /old-path(/|$)(.*)
        pathType: ImplementationSpecific
        backend:
          service:
            name: app-service
            port:
              number: 80

Enter fullscreen mode Exit fullscreen mode

After: Calico HTTPRoute (Gateway API)

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: rewrite-route
spec:
  parentRefs:
  - name: calico-ingress
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /old-path
    filters:
    - type: URLRewrite
      urlRewrite:
        path:
          type: ReplacePrefixMatch
          replacePrefixMatch: /
    - type: RequestHeaderModifier
      requestHeaderModifier:
        add:
        - name: X-Source
          value: Calico-Migration
    backendRefs:
    - name: app-service
      port: 80
Enter fullscreen mode Exit fullscreen mode

Example 3: Redirect to HTTPS

Use case: Redirect HTTP traffic to HTTPS using a 301.

Enforcing HTTPS is a production requirement for modern applications. In the legacy NGINX model, this was typically handled via a global or per-Ingress annotation. With Calico Ingress Gateway, since we configured an HTTPS listener in Step 2, we simply add a RequestRedirect filter to our HTTPRoute to enforce a permanent 301 redirect.

Before: NGINX Ingress (Legacy Configuration)

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: test-ingress
  annotations:
    kubernetes.io/ingress.class: "nginx"
    nginx.ingress.kubernetes.io/ssl-redirect: "true" 
spec:
  rules:
    - host: foo.example.com
      http:
        paths:
          - path: /your-api
            pathType: Prefix
            backend:
              service:
                name: api-service
                port:
                  number: 80
Enter fullscreen mode Exit fullscreen mode

After: Calico HTTPRoute (Gateway API)

Since we configured an HTTPS listener in step 2 we just need to create a filter to redirect everything to HTTPS with a 301 response code.

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: https-redirect
spec:
  parentRefs:
  - name: my-gateway
  hostnames:
  - "your-domain.com"
  rules:
  - filters:
    - type: RequestRedirect
      requestRedirect:
        scheme: https
        statusCode: 301
Enter fullscreen mode Exit fullscreen mode

Phase 3: Verification & Cutover Strategies

Running NGINX and Gateway API Side-by-Side

The final phase of the migration is moving your live traffic from NGINX to the Calico Ingress Gateway.

Since the two controllers can run simultaneously in the same cluster, you can perform a “canary” style cutover to minimize risk.

There are two ways to do this:

Cutover Option 1: Load Balancer Canary Traffic

Configure your load balancer to send a certain percentage of your traffic to the new gateway if there is support for it. For example:

  1. AWS Application Load Balancer (ALB): Supports canary releases using weighted target groups, allowing you to send a small percentage (e.g., 10%) of traffic to a new version while the rest goes to the old.
  2. Google Cloud Load Balancing: Offers capabilities for percentage-based canaries, often with Cloud Run or GKE, for fine-grained traffic control.
  3. Azure Load Balancer/Application Gateway: Can be orchestrated with tools for canary strategies, working with services like App Mesh.

Cutover Option 2: Parallel Load Balancer Testing

  1. Deploy a second load balancer to send traffic to the gateway
  2. Test using the external IP or DNS record
  3. Validate routing with curl: Use curl to test the Calico Gateway’s external IP directly to ensure the application responds correctly
# include the host header if you have hostnames configured in your HTTPRoutes
curl -H "Host: your-domain.com" http://<CALICO_GATEWAY_LOAD_BALANCER_IP>/your-api
Enter fullscreen mode Exit fullscreen mode

Final Traffic Cutover

Once you have verified that the Calico Ingress Gateway is routing traffic correctly, you can begin transitioning live traffic. In a production environment, your gateway will reside behind a load balancer to which your DNS will route requests.

Step 1: Redirect Traffic to the New Gateway

Choose the cutover strategy that best fits your infrastructure:

Option 1: DNS-Based Cutover (Parallel Load Balancers)

  • Reduce TTL: Lower the Time to Live (TTL) on your DNS records to 5 minutes or less before the move.
  • Update DNS: Change your DNS A-records or CNAMEs to point to the new Calico load balancer.
  • Maintain Fallback: Keep the legacy NGINX Controller and its load balancer active for 24–48 hours to allow for an immediate rollback if needed.

— OR —

Option 2: Load Balancer Canary Cutover

If you are using a cloud load balancer (e.g., AWS ALB, Azure App Gateway) that supports weighted target groups, incrementally adjust the weights to send 100% of traffic to the Calico Ingress Gateway.

Why this matters: Canary-based cutovers allow platform teams to validate real production traffic against the new gateway with minimal risk, making it easier to detect regressions before the final migration.

Step 2: Monitor the Cutover

Watch your Calico logs and metrics to ensure traffic is flowing correctly through the new gateway:

  • Flow Logs: Check Calico Enterprise/Cloud flow logs for successful 2xx/3xx response codes.
  • Envoy Metrics: Monitor upstream/downstream latency to ensure performance parity with the legacy setup.

Step 3: Decommissioning NGINX

After the DNS change has propagated and you have confirmed there is no more traffic hitting the old NGINX controller:

  • Delete the legacy Ingress resources: kubectl delete ingress <name>
  • Uninstall the NGINX Ingress Controller.
  • Clean up any remaining NGINX-specific ConfigMaps or Secrets.

Important: Only proceed after zero traffic is confirmed.

Troubleshooting & FAQ

Migrating infrastructure components can sometimes lead to unexpected behavior. Here are the most common questions and issues platform engineers encounter when moving to Calico Ingress Gateway.

Question: Can I run both NGINX Ingress and Calico Ingress Gateway at the same time?

Answer: Yes. Since they use different API resources (Ingress vs. HTTPRoute) and different controller implementations, they can run side-by-side. This is the recommended way to test your migration before cutting over.

Q: What happens if I have an existing Calico Network Policy?

A: Calico Ingress Gateway integrates natively with Calico Network Policies. You can apply policies directly to the Gateway pods to restrict which namespaces or services the Gateway is allowed to talk to, providing much tighter security than a standard NGINX deployment.

Q: I applied my HTTPRoute, but I’m getting a 404 error. What’s wrong?

A: Check the following configuration points:

  • ParentRef: Ensure the parentRefs name in your HTTPRoute matches the name of your Gateway resource exactly.
  • Namespace: By default, a Gateway only listens to routes in its own namespace. Confirm that the allowedRoutes field is configured to allow From: All or that a ReferenceGrant has been created for cross-namespace routing.
  • Hostname: If you defined a hostnames field in the HTTPRoute, ensure your curl request or browser is using that exact header.

Q: How do I view logs for the new Gateway?

A: Since the Gateway is powered by Envoy, you can view the traffic logs by checking the logs of the pods created in the tigera-gateway namespace

# get a list of gateway pods
kubectl get pods -n tigera-gateway
# check the logs
kubectl logs [your envoy gateway pod] -n tigera-gateway
Enter fullscreen mode Exit fullscreen mode

Appendix: Automating Certificate Management (TLS) with Cert-Manager

Manually requesting, applying, and rotating certificates creates significant administrative overhead. If at all possible, this process should be automated. In this example, we will use cert-manager and Let’s Encrypt.

1. Install cert-manager

Deploy the latest version of cert-manager to your cluster:

kubectl create -f https://github.com/cert-manager/cert-manager/releases/download/v1.17.2/cert-manager.yaml
Enter fullscreen mode Exit fullscreen mode

2. Enable Gateway API Support

Patch the cert-manager deployment to enable Gateway API integration. This allows cert-manager to monitor Gateway resources and issue certificates automatically as needed.

kubectl patch deployment -n cert-manager cert-manager --type='json' --patch '
[
  {
    "op": "add",
    "path": "/spec/template/spec/containers/0/args/-",
    "value": "--enable-gateway-api"
  }
]'
Enter fullscreen mode Exit fullscreen mode

3. Configure Let’s Encrypt ClusterIssuer

Deploy a cluster-scoped ClusterIssuer to configure Let’s Encrypt as your Certificate Authority. This is configured to verify domain ownership via the HTTP-01 challenge by routing validation traffic directly through the Calico Ingress Gateway.

kubectl create -f -<<EOF
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: <USER-YOUR-EMAIL-HERE>
    privateKeySecretRef:
      name: letsencrypt-account-key
    solvers:
    - http01:
        gatewayHTTPRoute:
          parentRefs:
          - kind: Gateway
            name: calico-demo-gw
            namespace: default
EOF
Enter fullscreen mode Exit fullscreen mode

4. Annotate Gateways for Certificate Management

Annotate each gateway that needs to handle HTTPS to link it with the ClusterIssuer

kubectl annotate --overwrite gateway/your-calico-ingress-gateway -n your-gateway-namespace cert-manager.io/cluster-issuer=letsencrypt
Enter fullscreen mode Exit fullscreen mode

Migration Summary

Migrating from the NGINX Ingress Controller to the Kubernetes Gateway API modernizes how ingress traffic is defined and managed in Kubernetes. While the Ingress API remains for backward compatibility, it has reached its limits and relies heavily on controller-specific annotations.

Using Gateway API with Calico Ingress Gateway gives platform teams a clear separation between infrastructure and application routing, with features like rewrites, redirects, header manipulation, and TLS expressed directly in the API. Ingress traffic is also governed by the same Calico Network Policies used for east-west traffic, creating a consistent and auditable security model.

This migration can be performed incrementally, with NGINX Ingress and Gateway API running side by side until the new configuration is fully validated. For teams responding to the retirement of NGINX Ingress, Calico Ingress Gateway provides a production-ready, low-risk path to align with the Kubernetes ecosystem’s direction.

Start Your Gateway API Migration with Calico

Convert existing NGINX Ingress resources automatically

Use the Ingress-to-Gateway API migration tool to generate HTTPRoute resources from your current NGINX Ingress configuration.

→ Ingress to Gateway API migration tool

See the migration in action

Follow along with a live, step-by-step walkthrough of migrating from NGINX Ingress to Calico Ingress Gateway.

→ Launch Arcade Demo

Get expert guidance from Tigera

Talk to the Tigera team about your ingress architecture and see Calico Ingress Gateway in action.

Request a Demo →

The post Migrating from Ingress NGINX to Calico Ingress Gateway: A Step-by-Step Guide appeared first on Tigera - Creator of Calico.

Top comments (0)