Configure multi-cluster ingress with regional routing

Last validated:

This guide shows how to expose an application deployed across two different clusters to your tailnet. A single MagicDNS name routes each Tailscale client to their closest cluster using regional routing.

This guide requires regional routing, which is available on the Premium and Enterprise plans and must be explicitly enabled on your tailnet. Without it, all clients are routed to a single backend regardless of geography.

  • 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 both replicas of the application that are in each cluster. Clients are automatically routed to the closest cluster based on their proximity to Tailscale's DERP regions.

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 on 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 the same Ingress is created in multiple clusters pointing to the same MagicDNS hostname, the operator registers proxy pods from each cluster as backends for a single Tailscale Service. The Tailscale control plane then routes each client to the geographically closest healthy backend using the same mechanism as high availability subnet routers.

Further exploration