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
Migration Guide: Table of Contents
-
Phase 1: Environment Setup & Gateway Deployment
- • Step 1.1: Enable the Gateway API Resource
- • Step 1.2: Verify Resource Availability
- • Step 1.3: Deploy and Configure the Gateway
-
Phase 2: Transitioning Ingress to HTTPRoute
- • Translation: NGINX Annotations to HTTPRoute Filters
- • Technical Examples: Path-Based, Rewrites, and HTTPS Redirects
-
Phase 3: Verification & Final Cutover
- • Parallel Testing and Traffic Weight Shifting
- • Post-Migration Monitoring and Decommissioning
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
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
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
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
Output:
{
"controllerName": "gateway.envoyproxy.io/gatewayclass-controller",
"parametersRef": {
"group": "gateway.envoyproxy.io",
"kind": "EnvoyProxy",
"name": "tigera-gateway-class",
"namespace": "tigera-gateway"
}
}
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
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
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
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
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
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
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
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
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
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
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:
- 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.
- Google Cloud Load Balancing: Offers capabilities for percentage-based canaries, often with Cloud Run or GKE, for fine-grained traffic control.
- 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
- Deploy a second load balancer to send traffic to the gateway
- Test using the external IP or DNS record
- 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
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
parentRefsname in yourHTTPRoutematches the name of yourGatewayresource exactly. -
Namespace: By default, a Gateway only listens to routes in its own namespace. Confirm that the
allowedRoutesfield is configured to allowFrom: Allor that aReferenceGranthas been created for cross-namespace routing. -
Hostname: If you defined a
hostnamesfield in theHTTPRoute, ensure yourcurlrequest 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
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
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"
}
]'
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
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
—
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.
Get expert guidance from Tigera
Talk to the Tigera team about your ingress architecture and see Calico Ingress Gateway in action.
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)