Configure multi-cluster ingress with regional routing

Last validated:

This guide shows how to expose an application deployed across two clusters to your tailnet. Clients use a single MagicDNS name. Tailscale uses regional routing to select a cluster for each client.

This guide requires regional routing, which is available on the Premium and Enterprise plans.

  • A ProxyGroup in each cluster manages a set of highly available proxies.
  • A Tailscale Ingress in each cluster configures the proxies to forward traffic to the backend Kubernetes Service configured on the Ingress.
  • A single Tailscale Service represents the application replicas in both clusters. Tailscale uses DERP latency measurements to select a region for each client.

This tutorial covers Layer 7 ingress using a Kubernetes Ingress resource. You can use the same approach for Layer 3 ingress using a Kubernetes Service resource.

Prerequisites

Before you begin, make sure you have the following:

Configure the clusters

Perform these steps in each cluster.

Create a ProxyGroup and ProxyClass

Apply the following manifest to manage the ingress proxies and configure them to use Let's Encrypt's staging environment for initial testing.

Set hostnamePrefix to a unique value per cluster (for example, eu-west, us-east). This determines the hostname of each cluster's proxy devices in your tailnet.

apiVersion: tailscale.com/v1alpha1
kind: ProxyGroup
metadata:
  name: ingress-proxies
spec:
  type: ingress
  hostnamePrefix: eu-west
  replicas: 2
  proxyClass: letsencrypt-staging
---
apiVersion: tailscale.com/v1alpha1
kind: ProxyClass
metadata:
  name: letsencrypt-staging
spec:
  useLetsEncryptStagingEnvironment: true
kubectl apply -f proxygroup.yaml

Deploy a sample application

If you don't have an existing workload, deploy this sample nginx application.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: nginx:latest
          ports:
            - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: nginx
spec:
  selector:
    app: nginx
  ports:
    - port: 80
      targetPort: 80
kubectl apply -f nginx.yaml

Create the Ingress resource

Expose the nginx service with a shared MagicDNS name. The spec.tls.hosts field determines the MagicDNS name. Use the same hostname in both clusters so they share a single Tailscale Service.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: nginx
  annotations:
    tailscale.com/proxy-group: ingress-proxies
spec:
  defaultBackend:
    service:
      name: nginx
      port:
        number: 80
  ingressClassName: tailscale
  tls:
    - hosts:
        - nginx
kubectl apply -f ingress.yaml

Test the staging environment

  1. Check the MagicDNS name:

    kubectl get ingress nginx
    
  2. Test traffic flow. Because you are using staging certificates, use the -k flag to bypass certificate warnings:

    curl -ksS https://nginx.<tailnet>.ts.net
    
  3. Verify in the Services that proxy pods from both clusters are listed as backends for the nginx service.

Switch to production certificates

Update the ProxyClass in each cluster to disable the staging environment:

apiVersion: tailscale.com/v1alpha1
kind: ProxyClass
metadata:
  name: letsencrypt-staging
spec:
  useLetsEncryptStagingEnvironment: false
kubectl apply -f proxyclass.yaml

Test the production environment

Confirm that traffic is served with a valid production certificate:

curl https://nginx.<tailnet>.ts.net

How regional routing works

When you create the same Ingress in multiple clusters with the same MagicDNS hostname, the Tailscale Kubernetes Operator registers their proxy pods as backends for one Tailscale Service. The control plane groups backends by DERP region. It selects a region for each client, then an available backend in that region. This uses the same mechanism as high availability subnet routers.

Switch to MagicRoute beta

If you switch your tailnet from regional routing to MagicRoute, Tailscale selects among the existing backends using priority scoring instead of DERP-region grouping. You do not need to recreate the Tailscale Service or Kubernetes Ingress resources.

Further exploration