Skip to main content

Migrate away from ingress-nginx

ingress-nginx is deprecated and will reach end-of-life in March 2026. This guide covers migration options for existing Spaces deployments.

For help choosing an exposure method, see Exposing Spaces Externally.

Prerequisites

Set environment variables used throughout this guide:

export SPACES_VERSION=<version>  # Example: 1.16.0
export SPACES_ROUTER_HOST=<hostname> # Example: proxy.example.com

Export your current Helm values to a file (or use an existing version-controlled file):

helm get values spaces -n upbound-system -o yaml > values.yaml

You'll merge new configuration into this file throughout the migration.

Migrate current Spaces version before March 2026

Choose your migration option:

OptionWhen to use
Gateway APIAlready using Gateway API or need shared gateway
TraefikMigrate from nginx Ingress to alternative controller

Export your current Helm values to a file (or use your existing values file if stored in Git):

helm get values spaces -n upbound-system -o yaml > values.yaml

Gateway API (Spaces 1.10+)

Gateway API support has been available since Spaces 1.10. See Gateway API Configuration for detailed setup instructions.

note

Pre-1.16 Spaces doesn't support running Ingress and Gateway API simultaneously. This migration requires switching over in a single upgrade, which causes brief downtime during DNS propagation.

Remove existing ingress resources

Delete the Ingress resource and ingress-nginx controller:

kubectl -n upbound-system delete ingress mxe-router-ingress
helm -n ingress-nginx delete ingress-nginx
warning

This step forces downtime for API access through spaces-router until the Gateway API configuration is complete.

Install a gateway API controller

Install a Gateway API implementation that supports TLS passthrough and TLSRoute.

The following example uses Envoy Gateway:

export ENVOY_GATEWAY_VERSION=<version> # Example: v1.2.4

helm -n envoy-gateway-system upgrade --install --wait --wait-for-jobs \
--timeout 300s --create-namespace envoy-gateway \
oci://docker.io/envoyproxy/gateway-helm \
--version "${ENVOY_GATEWAY_VERSION}"

Create GatewayClass resource

Create a GatewayClass resource.

kubectl apply -f - --server-side <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: spaces
spec:
controllerName: gateway.envoyproxy.io/gatewayclass-controller
parametersRef:
group: gateway.envoyproxy.io
kind: EnvoyProxy
name: spaces-proxy-config
namespace: envoy-gateway-system
EOF

Create Gateway resource

Create a Gateway resource in the upbound-system namespace.

kubectl apply -f - --server-side <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: spaces
namespace: upbound-system
spec:
gatewayClassName: spaces
listeners:
- name: tls
port: 443
protocol: TLS
allowedRoutes:
namespaces:
from: Same
tls:
mode: Passthrough
EOF

Update your Helm values

ingress:
provision: false

gatewayAPI:
host: proxy.example.com # Must match your current ingress.host
gateway:
provision: true
name: spaces
className: spaces # Must match your GatewayClass name
spacesRouterRoute:
provision: true
# Labels for NetworkPolicy - must match your gateway controller's pods
podLabels:
app.kubernetes.io/name: envoy
app.kubernetes.io/component: proxy
app.kubernetes.io/managed-by: envoy-gateway
namespaceLabels:
kubernetes.io/metadata.name: envoy-gateway-system

Get the load balancer hostname

Check the externally routable hostname for the Gateway's load balancer. The Helm gatewayAPI.host parameter requires this hostname.

For Envoy Gateway, inspect the LoadBalancer service:

kubectl get service -n envoy-gateway-system \
-l gateway.envoyproxy.io/owning-gateway-name=spaces \
-o jsonpath='{.items[0].status.loadBalancer.ingress[0].hostname}'

Upgrade the Spaces Helm release

Upgrade the Spaces installation with Gateway API parameters:

helm -n upbound-system upgrade spaces \
oci://xpkg.upbound.io/spaces-artifacts/spaces \
--version "${SPACES_VERSION}" \
--set "ingress.provision=false" \
--set "gatewayAPI.host=${GATEWAY_HOSTNAME}" \
--set "account=${UPBOUND_ACCOUNT}" \
--reuse-values \
--wait

Restart spaces-router (optional)

If the gatewayAPI.host value differs from the previous ingress.host value, restart the spaces-router pod to regenerate the certificate with the correct SAN:

kubectl -n upbound-system rollout restart deployment spaces-router
kubectl -n upbound-system rollout status deployment spaces-router

Configure values.yaml

Update your values.yaml to disable Ingress and enable Gateway API:

ingress:
provision: false

gatewayAPI:
host: proxy.example.com # Must match your current ingress.host
gateway:
provision: true
name: spaces
className: spaces # Must match your GatewayClass name
spacesRouterRoute:
provision: true
# Labels for NetworkPolicy - must match your gateway controller's pods
podLabels:
app.kubernetes.io/name: envoy
app.kubernetes.io/component: proxy
app.kubernetes.io/managed-by: envoy-gateway
namespaceLabels:
kubernetes.io/metadata.name: envoy-gateway-system

Upgrade Spaces

This disables Ingress and enables Gateway API:

helm upgrade spaces oci://xpkg.upbound.io/spaces-artifacts/spaces \
--version ${SPACES_VERSION} \
--namespace upbound-system \
-f values.yaml \
--wait

Get the gateway address and update DNS

kubectl get gateway -n upbound-system spaces -o jsonpath='{.status.addresses[0].value}'

Update your DNS record to this address.

Verify connectivity

curl -v "https://${SPACES_ROUTER_HOST}/version"
# Expected: 401 Unauthorized (routing works, auth required)

Uninstall ingress-nginx

helm uninstall ingress-nginx --namespace ingress-nginx

Traefik (or alternative ingress controller)

Traefik can pick up the existing nginx Ingress via --providers.kubernetesIngressNGINX. See the Traefik migration guide for details.

1. Install Traefik with nginx Ingress provider

helm repo add traefik https://traefik.github.io/charts
helm repo update
helm upgrade --install traefik traefik/traefik \
--create-namespace --namespace traefik \
--set 'service.type=LoadBalancer' \
--set 'additionalArguments={--providers.kubernetesIngressNGINX}' \
--wait

Configure Traefik's Service with NLB annotations. See Cloud-specific annotations.

2. Validate before switching DNS

# Get Traefik load balancer address
TRAEFIK_LB=$(kubectl get svc -n traefik traefik -o jsonpath='{.status.loadBalancer.ingress[0].hostname}')

# Test connectivity using --connect-to to route to Traefik
curl --connect-to "${SPACES_ROUTER_HOST}:443:${TRAEFIK_LB}:443" "https://${SPACES_ROUTER_HOST}/version"
# Expected: 401 Unauthorized (routing works, auth required)

3. Update DNS to point to Traefik

kubectl get svc -n traefik traefik -o jsonpath='{.status.loadBalancer.ingress[0].hostname}'

Update your DNS record to this address. For gradual migration, use weighted DNS routing.

4. Preserve the nginx IngressClass before uninstalling ingress-nginx

helm upgrade ingress-nginx ingress-nginx \
--repo https://kubernetes.github.io/ingress-nginx \
--namespace ingress-nginx \
--reuse-values \
--set-json 'controller.ingressClassResource.annotations={"helm.sh/resource-policy": "keep"}'

5. Uninstall ingress-nginx

helm uninstall ingress-nginx --namespace ingress-nginx

Keep ingress.provision: true so the Spaces chart continues to manage the Ingress resource. Traefik picks it up via the nginx provider.

Verification

After migration, verify connectivity:

curl -v "https://${SPACES_ROUTER_HOST}/version"
# Expected: 401 Unauthorized