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.
Upgrading to Spaces 1.16+
Choose your migration option:
| Option | When to use |
|---|---|
| LoadBalancer Service | Simplest setup, no additional components needed |
| Gateway API | Already using Gateway API or need shared gateway |
| Alternative ingress controller | Already using Ingress, or need shared load balancer |
All paths follow the same process: upgrade to 1.16+, switch exposure method, then uninstall ingress-nginx.
Configure your Ingress controller's Service with NLB annotations. See Cloud-specific annotations.
1. Install your chosen Ingress controller
2. Update the ingress configuration in your values.yaml
ingress:
provision: true
host: proxy.example.com
ingressClassName: <your-ingress-class>
annotations:
# Add your controller's TLS passthrough annotations
podLabels:
# Labels matching your controller's pods
namespaceLabels:
# Labels matching your controller's namespace
3. Upgrade Spaces
helm upgrade spaces oci://xpkg.upbound.io/spaces-artifacts/spaces \
--version ${SPACES_VERSION} \
--namespace upbound-system \
-f values.yaml \
--wait
4. Get the new load balancer address and update DNS
kubectl get svc -n <controller-namespace> <controller-service> -o jsonpath='{.status.loadBalancer.ingress[0].hostname}'
5. Uninstall ingress-nginx
helm uninstall ingress-nginx --namespace ingress-nginx
Migrate current Spaces version before March 2026
Choose your migration option:
| Option | When to use |
|---|---|
| Gateway API | Already using Gateway API or need shared gateway |
| Traefik | Migrate 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.
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
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