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.

Options vary by Spaces version. Select your Spaces version:

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:

OptionWhen to use
LoadBalancer ServiceSimplest setup, no additional components needed
Gateway APIAlready using Gateway API or need shared gateway
Alternative ingress controllerAlready using Ingress, or need shared load balancer

All paths follow the same process: upgrade to 1.16+, switch exposure method, then uninstall ingress-nginx.

Upgrade to 1.16+ with Updated Ingress Values

Spaces doesn't provision the Ingress resource by default and is now controller-agnostic.

Add the following to your values.yaml to keep ingress-nginx working:

ingress:
provision: true
host: proxy.example.com # Replace with your existing hostname
ingressClassName: nginx
annotations:
nginx.ingress.kubernetes.io/ssl-passthrough: "true"
nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
podLabels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/component: controller
namespaceLabels:
kubernetes.io/metadata.name: ingress-nginx

Upgrade Spaces to 1.16+:

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

Verify ingress-nginx is still working before you contine.

This section describes how to expose the spaces-router with a LoadBalancer Service.

important

Use a Network Load Balancer (L4), not an Application Load Balancer (L7). Spaces uses long-lived connections for watch traffic that L7 load balancers may timeout.

1. Add the LoadBalancer Service configuration to your values.yaml

externalTLS:
host: proxy.example.com # Must match your ingress.host

router:
proxy:
service:
type: LoadBalancer
annotations:
# AWS NLB (see Cloud-Specific Annotations for other clouds)
service.beta.kubernetes.io/aws-load-balancer-type: external
service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
note

This example uses AWS-specific annotations. See Cloud-Specific Annotations for GCP and Azure.

2. Upgrade Spaces (Ingress stays running during transition)

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

3. Get the new LoadBalancer address

kubectl get svc -n upbound-system spaces-router \
-o jsonpath='{.status.loadBalancer.ingress[0].hostname}'

4. Validate before switching DNS

# Get spaces-router load balancer address
ROUTER_LB=$(kubectl get svc -n upbound-system spaces-router -o jsonpath='{.status.loadBalancer.ingress[0].hostname}')

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

5. Update your DNS record to point to the new LoadBalancer address

6. Update your values.yaml to disable Ingress, then upgrade Spaces

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

7. Uninstall ingress-nginx

helm uninstall ingress-nginx --namespace ingress-nginx

Gateway API

Spaces supports the Gateway API as an alternative to Ingress. Gateway API is the Kubernetes standard for traffic routing going forward.

1. Install Envoy Gateway

helm install eg oci://docker.io/envoyproxy/gateway-helm \
--namespace envoy-gateway-system \
--create-namespace \
--wait

See Envoy Gateway installation docs for more detailed instructions.

2. Create a GatewayClass

Create a GatewayClass resource appropriate for your cloud provider.

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

3. Configure Spaces Helm Values

ingress:
provision: false # Disable Ingress when using Gateway API

gatewayAPI:
host: proxy.example.com
gateway:
provision: true
name: spaces
className: spaces
spacesRouterRoute:
provision: true
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

4. Install or Upgrade Spaces

See Spaces installation docs for detailed installation instructions.

helm upgrade spaces oci://xpkg.upbound.io/spaces-artifacts/spaces \
--namespace upbound-system \
-f values.yaml \
--wait

5. Get the Gateway Address

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

Create or update a DNS record pointing your gatewayAPI.host to this address.

note

If you're having issues with your setup, verify the configuration with these troubleshooting steps:

  • Check Gateway Status

    kubectl get gateway -n upbound-system spaces -o yaml

    Look for status.conditions - the Gateway should be Accepted and Programmed.

  • Check TLSRoute Status

    kubectl get tlsroute -n upbound-system spaces-router -o yaml

    The route should show Accepted: True in its status.

  • Verify Connectivity

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

6. Validate before switching DNS

# Get Gateway address
GATEWAY_LB=$(kubectl get gateway -n upbound-system spaces -o jsonpath='{.status.addresses[0].value}')

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

7. Update your DNS record to point to the new Gateway address

8. Update your values.yaml to disable Ingress, then upgrade Spaces:

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

9. Uninstall ingress-nginx

helm uninstall ingress-nginx --namespace ingress-nginx

Alternative ingress controller

Use any Ingress controller that supports TLS passthrough.

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

Can't upgrade to Spaces 1.16 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][gateway-api-config] 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=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 appropriate for your cloud provider.

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
note

During installation or upgrade, you can use the Spaces Helm chart to create the Gateway automatically with these parameters:

  • gatewayAPI.gateway.provision=true
  • gatewayAPI.gateway.className=spaces

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