{"slug":"expose-a-kubernetes-cluster-workload-to-your-tailnet-cluster-ingress","title":"Expose a Kubernetes cluster workload to your tailnet (cluster ingress)","tags":["tailscale","containers"],"agent_summary":"Last validated: Jan 5, 2026","trigger_phrases":[],"runnable":false,"markdown":"\r\n# Expose a Kubernetes cluster workload to your tailnet (cluster ingress)\r\n\r\nLast validated: Jan 5, 2026\r\n\r\nExposing a Kubernetes cluster workload to your tailnetis currently [in beta](https://tailscale.com/docs/reference/tailscale-release-stages#beta).\r\n\r\nYou can use the Tailscale Kubernetes operator to expose a Kubernetes cluster workload to your tailnet in three ways:\r\n\r\n- Create a `LoadBalancer` type `Service` with the `tailscale` [`loadBalancerClass`](https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress#loadbalancerclass) that fronts your workload.\r\n- [Annotate](https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress#annotations) an existing `Service` that fronts your workload.\r\n- Create an [`Ingress` resource](https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress#ingress-resource) fronting a `Service` or `Service`s for the workloads you wish to expose.\r\n\r\n## [Prerequisites](https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress\\#prerequisites)\r\n\r\n- [Set up the Kubernetes Operator](https://tailscale.com/docs/features/kubernetes-operator#setup).\r\n\r\n## [Exposing a cluster workload using a Kubernetes `Service`](https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress\\#exposing-a-cluster-workload-using-a-kubernetes-service)\r\n\r\n### [Exposing a cluster workload by using a Tailscale Load Balancer Service](https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress\\#exposing-a-cluster-workload-by-using-a-tailscale-load-balancer-service)\r\n\r\nCreate a new Kubernetes `Service` of type `LoadBalancer`:\r\n\r\n1. Set `spec.type` to `LoadBalancer`.\r\n2. Set `spec.loadBalancerClass` to `tailscale`.\r\n\r\nAfter provisioning completes, the Service status will show the [fully-qualified domain name](https://tailscale.com/docs/features/magicdns) of the Service in your tailnet. You can review the Service status by running `kubectl get service <service name>`.\r\n\r\nYou should also notice a new node with that name appear in the [Machines](https://login.tailscale.com/admin/machines) page of the admin console.\r\n\r\n### [Exposing a cluster workload by annotating an existing `Service`](https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress\\#exposing-a-cluster-workload-by-annotating-an-existing-service)\r\n\r\nIf the `Service` you want to expose already exists, you can expose it to Tailscale using [object annotations](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations).\r\n\r\nEdit the `Service` and under `metadata.annotations`, add the annotation `tailscale.com/expose` with the value `\"true\"`. Note that `\"true\"` is quoted because annotation values are strings, and an unquoted `true` will be incorrectly interpreted as a boolean.\r\n\r\nIn this mode, Kubernetes doesn't tell you the Tailscale machine name. You can look up the node in the [Machines](https://login.tailscale.com/admin/machines) page of the admin console to find its machine name. By default, the machine name of an exposed `Service` is `<k8s-namespace>-<k8s-servicename>`, but it [can be changed](https://tailscale.com/docs/features/kubernetes-operator/how-to/customize#custom-machine-names).\r\n\r\nExposing headless `Service`s is not supported.\r\n\r\n## [Exposing cluster workloads using a Kubernetes `Ingress`](https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress\\#exposing-cluster-workloads-using-a-kubernetes-ingress)\r\n\r\nYou can expose cluster workloads either to your tailnet or the public internet over TLS using an `Ingress` resource.\r\nWhen using an `Ingress` resource, you also get the ability to identify callers using [HTTP headers](https://tailscale.com/docs/features/tailscale-serve#identity-headers) injected by the `Ingress` proxy.\r\n\r\n`Ingress` resources only support TLS, and are only exposed over HTTPS using a [MagicDNS](https://tailscale.com/docs/features/magicdns) name and publicly\r\ntrusted certificates from Let's Encrypt. You must [enable HTTPS](https://tailscale.com/docs/how-to/set-up-https-certificates) and [MagicDNS](https://tailscale.com/docs/features/magicdns) on your\r\ntailnet.\r\n\r\nEdit the `Ingress` resource you want to expose to use the `Ingress` class `tailscale`:\r\n\r\n1. Set `spec.ingressClassName` to `tailscale`.\r\n2. Set `tls.hosts` to the desired host name of the Tailscale node. Only the first label is used. Refer to [custom machine names](https://tailscale.com/docs/features/kubernetes-operator/how-to/customize#custom-machine-names) for more information.\r\n\r\nFor example, to expose an `Ingress` resource `nginx` to your tailnet:\r\n\r\n```yaml\r\napiVersion: networking.k8s.io/v1\r\nkind: Ingress\r\nmetadata:\r\n  name: nginx\r\nspec:\r\n  defaultBackend:\r\n    service:\r\n      name: nginx\r\n      port:\r\n        number: 80\r\n  ingressClassName: tailscale\r\n  tls:\r\n    - hosts:\r\n        - nginx\r\n```\r\n\r\nThe backend is HTTP by default. To use HTTPS on the backend, either set the port name to `https` or the port number to `443`:\r\n\r\n```yaml\r\napiVersion: networking.k8s.io/v1\r\nkind: Ingress\r\nmetadata:\r\n  name: nginx\r\nspec:\r\n  defaultBackend:\r\n    service:\r\n      name: nginx\r\n      port:\r\n        name: https\r\n  ingressClassName: tailscale\r\n---\r\napiVersion: v1\r\nkind: Service\r\nmetadata:\r\n  name: nginx\r\nspec:\r\n  ports:\r\n    - name: https\r\n      port: 443\r\n      targetPort: 443\r\n  type: ClusterIP\r\n```\r\n\r\nA single `Ingress` resource can be used to front multiple backend `Services`:\r\n\r\n```yaml\r\napiVersion: networking.k8s.io/v1\r\nkind: Ingress\r\nmetadata:\r\n  name: ingress\r\nspec:\r\n  ingressClassName: tailscale\r\n  rules:\r\n    - http:\r\n        paths:\r\n          - path: /\r\n            pathType: Prefix\r\n            backend:\r\n              service:\r\n                name: ui-svc\r\n                port:\r\n                  number: 80\r\n          - path: /api\r\n            pathType: Prefix\r\n            backend:\r\n              service:\r\n                name: api-svc\r\n                port:\r\n                  number: 80\r\n```\r\n\r\nCurrently the only supported [`Ingress` path type](https://kubernetes.io/docs/concepts/services-networking/ingress/#path-types) is `Prefix`. Requests for paths with other path types will be routed according to `Prefix` rules.\r\n\r\nA Tailscale `Ingress` can only be accessed on port 443.\r\n\r\n## [Exposing a `Service` to the public internet using `Ingress` and Tailscale Funnel](https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress\\#exposing-a-service-to-the-public-internet-using-ingress-and-tailscale-funnel)\r\n\r\nYou can also use the Tailscale Kubernetes Operator to expose an `Ingress` resource in your Kubernetes cluster to the public internet using [Tailscale Funnel](https://tailscale.com/docs/features/tailscale-funnel). To do so:\r\n\r\n1. Add a `tailscale.com/funnel: \"true\"` annotation:\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n```yaml\r\napiVersion: networking.k8s.io/v1\r\nkind: Ingress\r\nmetadata:\r\n     name: funnel\r\n     annotations:\r\n       tailscale.com/funnel: \"true\"\r\nspec:\r\n     defaultBackend:\r\n       service:\r\n         name: funnel\r\n         port:\r\n           number: 80\r\n     ingressClassName: tailscale\r\n     tls:\r\n    - hosts:\r\n        - funnel\r\n```\r\n\r\n2. Update the access control policies for your tailnet to allow Kubernetes Operator proxy services to use Tailscale Funnel.\r\n\r\n\r\nAdd a [node attribute](https://tailscale.com/docs/features/tailscale-funnel#requirements-and-limitations) to allow nodes created by the Kubernetes operator to use Funnel:\r\n\r\n```json\r\n\"nodeAttrs\": [\\\r\n{\\\r\n    \"target\": [\"tag:k8s\"], // tag that Tailscale Operator uses to tag proxies; defaults to 'tag:k8s'\\\r\n    \"attr\":   [\"funnel\"],\\\r\n},\\\r\n// Additional noteAttrs as needed\\\r\n]\r\n```\r\n\r\nNote that even if your policy has the `funnel` attribute assigned to `autogroup:member` (the default), you still need to add it to the tag used by proxies because `autogroup:member` does not include tagged devices.\r\n\r\n## [Removing a Service](https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress\\#removing-a-service)\r\n\r\nAny of the following actions remove a Kubernetes `Service` you exposed from your tailnet:\r\n\r\n- Delete the `Service` entirely.\r\n- If you use the `tailscale.com/expose` annotation, remove the annotation.\r\n- If you use an `Ingress` resource, delete it or change or unset `spec.ingressClassName`.\r\n\r\nDeleting a `Service`'s Tailscale node in the [Machines](https://login.tailscale.com/admin/machines) page does not clean up the Kubernetes state associated with that `Service`.\r\n\r\n## [High availability](https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress\\#high-availability)\r\n\r\nTailscale versions 1.84 and later support deploying Tailscale Kubernetes Operator's ingress proxies in high availability (HA) mode, using [`ProxyGroup`](https://tailscale.com/docs/features/kubernetes-operator#proxy-group) and a new Tailscale feature, [Tailscale Services](https://tailscale.com/docs/features/tailscale-services).\r\n\r\nThe HA mode lets you:\r\n\r\n- Expose a Kubernetes `Service` or `Ingress` resource to your tailnet through multiple active ingress proxies, to prevent downtime during proxy `Pod` restarts.\r\n\r\n- Expose many `Service` and `Ingress` resources to your tailnet using a smaller number of proxy `Pod`s.\r\n\r\n\r\n### [Prerequisites](https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress\\#prerequisites-1)\r\n\r\n- Ensure that the OAuth client credentials [used by the Tailscale Kubernetes Operator](https://tailscale.com/docs/features/kubernetes-operator#setup) have `Services`, `Devices Core` and `Auth Keys` write scopes.\r\n\r\n\r\n\r\nIf you are updating credentials for an existing installation, you must recreate the Kubernetes Operator's `Pod`.\r\n\r\n- Create a [`ProxyGroup`](https://tailscale.com/docs/features/kubernetes-operator#proxy-group) with `spec.type` set to `ingress`:\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n```yaml\r\napiVersion: tailscale.com/v1alpha1\r\nkind: ProxyGroup\r\nmetadata:\r\n    name: ingress-proxies\r\nspec:\r\n    type: ingress\r\n```\r\n\r\n\r\nRefer to the [`ProxyGroup` API documentation](https://github.com/tailscale/tailscale/blob/main/k8s-operator/api.md#proxygroup) for all available configuration options.\r\n\r\n### [Expose a Tailscale Ingress in HA mode](https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress\\#expose-a-tailscale-ingress-in-ha-mode)\r\n\r\n- [Configure permissions](https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress#permissions)\r\n\r\n- Create a Tailscale `Ingress` resource that references the `ProxyGroup` you created in the previous step.\r\nThe `spec.tls.hosts` field can contain (at most) a single entry that determines the first label of the DNS name by which the `Ingress` will be exposed to the tailnet. If unset, it defaults to `<ingress-name>-<namespace>`.\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n```yaml\r\napiVersion: networking.k8s.io/v1\r\nkind: Ingress\r\nmetadata:\r\n    name: nginx\r\n    annotations:\r\n      tailscale.com/proxy-group: ingress-proxies\r\nspec:\r\n    defaultBackend:\r\n      service:\r\n        name: nginx\r\n        port:\r\n          number: 80\r\n    ingressClassName: tailscale\r\n    tls:\r\n    - hosts:\r\n        - nginx\r\n```\r\n\r\n- Wait for the `Ingress` resource to become ready.\r\n\r\n\r\n```shell\r\nkubectl wait --timeout=80s  ingress nginx --for=jsonpath='{.status.loadBalancer.ingress[0].ports[0].port}'=443\r\n```\r\n\r\n- Ensure that your Tailscale client [accepts routes](https://tailscale.com/docs/features/subnet-routers#use-your-subnet-routes-from-other-devices). Clients other than Linux accept routes by default.\r\n\r\n- Access the cluster workload from a Tailscale client:\r\n\r\n\r\n```shell\r\nkubectl get ingress nginx\r\nNAME    CLASS       HOSTS   ADDRESS                  PORTS     AGE\r\nnginx tailscale   *       nginx.tailxyz.ts.net   80, 443   15s\r\n```\r\n\r\n```shell\r\ncurl https://nginx.tailxyz.ts.net\r\n...\r\n```\r\n\r\n### [Expose a Tailscale Service in HA mode](https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress\\#expose-a-tailscale-service-in-ha-mode)\r\n\r\n- [Configure permissions](https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress#permissions)\r\n\r\n- Create a Tailscale LoadBalancer `Service` that references the `ProxyGroup` you created in the previous step.\r\nYou can use the `tailscale.com/hostname` annotation to set the first label of the DNS name by which the `Service` will be exposed to the tailnet. If unset, it defaults to `<service-name>-<namespace>`.\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n```yaml\r\napiVersion: v1\r\nkind: Service\r\nmetadata:\r\n    name: nginx\r\n    annotations:\r\n      tailscale.com/proxy-group: ingress-proxies\r\n      tailscale.com/hostname: nginx\r\nspec:\r\n    ports:\r\n    - name: http\r\n      port: 80\r\n      targetPort: 80\r\ntype: LoadBalancer\r\nloadBalancerClass: tailscale\r\n```\r\n\r\n- Wait for the resources to be configured.\r\n\r\n\r\n```shell\r\nkubectl wait svc nginx --for condition=TailscaleIngressSvcConfigured\r\n```\r\n\r\n- Access the cluster workload from the tailnet.\r\n\r\n```shell\r\nkubectl get svc nginx\r\nNAME    TYPE           CLUSTER-IP      EXTERNAL-IP     PORT(S)        AGE\r\nnginx LoadBalancer   10.96.194.223   100.91.19.147   80:31967/TCP   5m16s\r\n```\r\n\r\n```shell\r\ncurl http://nginx.tailxyz.ts.net\r\n...\r\n```\r\n\r\nYou can expose an [annotated Tailscale ingress Service](https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress#annotations) in the same way.\r\n\r\n### [Configure permissions](https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress\\#configure-permissions)\r\n\r\nBy default, the `ProxyGroup` proxies are [tagged](https://tailscale.com/docs/features/tags) with the tag `tag:k8s`.\r\nYou can configure tags using the `.tags` field in the [`ProxyGroup` spec](https://github.com/tailscale/tailscale/blob/main/k8s-operator/api.md#proxygroup).\r\nYou must ensure that the tag by which you tagged the [operator's OAuth client credentials](https://tailscale.com/docs/features/kubernetes-operator#setup) is a [`tagOwner`](https://tailscale.com/docs/reference/syntax/policy-file#tag-owners) of the `ProxyGroup` device tags.\r\n\r\nUnlike with non-HA proxies, the proxy tags are not used to grant access to the cluster apps exposed using the proxies.\r\n\r\nFor each HA `Service` or `Ingress` exposed on a `ProxyGroup`, the Kubernetes Operator creates a Tailscale Service.\r\nEach `ProxyGroup` proxy advertises the Tailscale Service, by configuring itself as a backend for the tailnet traffic for the Tailscale Service.\r\nThe Tailscale IP address and DNS name given to the `Ingress` or `Service` are the IP addresses and DNS name of the Tailscale Service.\r\n\r\nYou can [tag](https://tailscale.com/docs/features/tags) Tailscale Service and use the tag to configure which tailnet devices can advertise the Service and which tailnet identities can access it.\r\nBy default, the Kubernetes Operator tags all Tailscale Services with a tag `tag:k8s`.\r\nYou can configure Tailscale Service tags using `tailscale.com/tags` annotation on the `Service` or `Ingress` resource.\r\n\r\n#### [Ensure that `ProxyGroup` devices can advertise the Tailscale Service](https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress\\#ensure-that-proxygroup-devices-can-advertise-the-tailscale-service)\r\n\r\nTo permit `ProxyGroup` devices to advertise a Tailscale Service, use the [`autoApprovers`](https://tailscale.com/docs/reference/syntax/policy-file#autoapprovers) section of the tailnet policy file.\r\n\r\nFor example, to let `ProxyGroup` devices with the tag `tag:eu-cluster` to advertise Tailscale Services with tag `tag:monitoring`, add the following to your tailnet policy file:\r\n\r\n```json\r\n\"autoApprovers\": {\r\n\"services\": {\r\n    \"tag:monitoring\": [\"tag:eu-cluster\"],\r\n},\r\n}\r\n```\r\n\r\n#### [Configure access](https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress\\#configure-access)\r\n\r\nYou can use Tailscale Service tags to control access to it.\r\n\r\nFor example, to let the user group `group:eng` to access Tailscale Services with the tag `tag:monitoring` exposed on a `ProxyGroup` with the tag `tag:eu-cluster`, add the following to your tailnet policy file:\r\n\r\n```json\r\n\"grants\": [\\\r\n{\\\r\n    \"src\": [\"group:eng\"],\\\r\n    \"dst\": [\"tag:monitoring\"],\\\r\n    \"ip\":  [\"*\"],\\\r\n},\\\r\n{\\\r\n    \"src\": [\"group:eng\"],\\\r\n    \"dst\": [\"tag:eu-cluster:*\"],\\\r\n    \"ip\":  [\"icmp:*\"],\\\r\n},\\\r\n]\r\n```\r\n\r\nThe requirement to permit access to the `ProxyGroup` devices to access Tailscale Services is a temporary limitation.\r\n\r\n## [IPv6 support](https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress\\#ipv6-support)\r\n\r\n### [Ingress](https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress\\#ingress)\r\n\r\nTo proxy traffic to IPv6 backends, you might need to disable IPv4 tailnet addresses for the proxy tailnet nodes.\r\n\r\nYou need to disable IPv4 tailnet addresses for:\r\n\r\n- Proxies used to expose cluster workloads using [Tailscale ingress `Service`s](https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress) if they are running in a cluster that allocates IPv6 addresses to `Service`s.\r\n\r\n- Proxies used to expose cloud services using [Tailscale ExternalName `Service`s](https://tailscale.com/docs/features/kubernetes-operator/how-to/cloud-services) if the cloud services have IPv6 addresses.\r\n\r\n\r\nYou can disable tailnet IPv4 addresses for a specific [tag](https://tailscale.com/docs/features/tags) using a `disable-ipv4` [node attribute](https://tailscale.com/docs/reference/syntax/policy-file#nodeattrs).\r\n\r\nThe following node attributes configuration example disables IPv4 addresses for all nodes tagged with `tag:k8s`:\r\n\r\n```json\r\n\"nodeAttrs\": [\\\r\n{\\\r\n    \"target\": [\"tag:k8s\"],\\\r\n    \"attr\": [\\\r\n      \"disable-ipv4\",\\\r\n    ],\\\r\n},\\\r\n]\r\n```\r\n\r\nTailnet IPv6 connectivity does not depend on host support for IPv6, so you can disable IPv4 addresses for nodes running on hosts that do not support IPv6.\r\n\r\nSimilarly, tailnet clients can connect to proxies with only tailnet IPv6 addresses even if they aren't running on hosts with IPv6 support.\r\n\r\n## [Customization](https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress\\#customization)\r\n\r\n[Customize the operator and resources it manages](https://tailscale.com/docs/features/kubernetes-operator/how-to/customize).\r\n\r\n## [Troubleshooting](https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress\\#troubleshooting)\r\n\r\n[Troubleshoot the operator and resources it manages](https://tailscale.com/docs/reference/troubleshooting/containers/kubernetes-operator).\r\n\r\n## [Limitations](https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress\\#limitations)\r\n\r\n- Tags are only considered during initial provisioning. That is, editing `tailscale.com/tags` on an already exposed `Service` doesn't update the tags until you clean up and re-expose the `Service`.\r\n- The requested machine name is only considered during initial provisioning. That is, editing `tailscale.com/hostname` on an already exposed `Service` doesn't update the machine name until you clean up and re-expose the `Service`.\r\n- Cluster-ingress using Kubernetes `Ingress` resource requires TLS certificates. Currently, the certificates are provisioned on the first connect. This means that the first connection might be slow or even time out.\r\n\r\n![Project Logo](https://cdn.brandfetch.io/tailscale.com/fallback/lettermark/theme/dark/h/256/w/256/icon?c=1bfwsmEH20zzEfSNTed)\r\n\r\nAsk AI\r\n\r\nreCAPTCHA\r\n\r\nRecaptcha requires verification.\r\n\r\nprotected by **reCAPTCHA**\r\n","html":"<h1>Expose a Kubernetes cluster workload to your tailnet (cluster ingress)</h1>\n<p>Last validated: Jan 5, 2026</p>\n<p>Exposing a Kubernetes cluster workload to your tailnetis currently <a href=\"https://tailscale.com/docs/reference/tailscale-release-stages#beta\">in beta</a>.</p>\n<p>You can use the Tailscale Kubernetes operator to expose a Kubernetes cluster workload to your tailnet in three ways:</p>\n<ul>\n<li>Create a <code>LoadBalancer</code> type <code>Service</code> with the <code>tailscale</code> <a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress#loadbalancerclass\"><code>loadBalancerClass</code></a> that fronts your workload.</li>\n<li><a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress#annotations\">Annotate</a> an existing <code>Service</code> that fronts your workload.</li>\n<li>Create an <a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress#ingress-resource\"><code>Ingress</code> resource</a> fronting a <code>Service</code> or <code>Service</code>s for the workloads you wish to expose.</li>\n</ul>\n<h2><a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress#prerequisites\">Prerequisites</a></h2>\n<ul>\n<li><a href=\"https://tailscale.com/docs/features/kubernetes-operator#setup\">Set up the Kubernetes Operator</a>.</li>\n</ul>\n<h2><a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress#exposing-a-cluster-workload-using-a-kubernetes-service\">Exposing a cluster workload using a Kubernetes <code>Service</code></a></h2>\n<h3><a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress#exposing-a-cluster-workload-by-using-a-tailscale-load-balancer-service\">Exposing a cluster workload by using a Tailscale Load Balancer Service</a></h3>\n<p>Create a new Kubernetes <code>Service</code> of type <code>LoadBalancer</code>:</p>\n<ol>\n<li>Set <code>spec.type</code> to <code>LoadBalancer</code>.</li>\n<li>Set <code>spec.loadBalancerClass</code> to <code>tailscale</code>.</li>\n</ol>\n<p>After provisioning completes, the Service status will show the <a href=\"https://tailscale.com/docs/features/magicdns\">fully-qualified domain name</a> of the Service in your tailnet. You can review the Service status by running <code>kubectl get service &#x3C;service name></code>.</p>\n<p>You should also notice a new node with that name appear in the <a href=\"https://login.tailscale.com/admin/machines\">Machines</a> page of the admin console.</p>\n<h3><a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress#exposing-a-cluster-workload-by-annotating-an-existing-service\">Exposing a cluster workload by annotating an existing <code>Service</code></a></h3>\n<p>If the <code>Service</code> you want to expose already exists, you can expose it to Tailscale using <a href=\"https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations\">object annotations</a>.</p>\n<p>Edit the <code>Service</code> and under <code>metadata.annotations</code>, add the annotation <code>tailscale.com/expose</code> with the value <code>\"true\"</code>. Note that <code>\"true\"</code> is quoted because annotation values are strings, and an unquoted <code>true</code> will be incorrectly interpreted as a boolean.</p>\n<p>In this mode, Kubernetes doesn't tell you the Tailscale machine name. You can look up the node in the <a href=\"https://login.tailscale.com/admin/machines\">Machines</a> page of the admin console to find its machine name. By default, the machine name of an exposed <code>Service</code> is <code>&#x3C;k8s-namespace>-&#x3C;k8s-servicename></code>, but it <a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/customize#custom-machine-names\">can be changed</a>.</p>\n<p>Exposing headless <code>Service</code>s is not supported.</p>\n<h2><a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress#exposing-cluster-workloads-using-a-kubernetes-ingress\">Exposing cluster workloads using a Kubernetes <code>Ingress</code></a></h2>\n<p>You can expose cluster workloads either to your tailnet or the public internet over TLS using an <code>Ingress</code> resource.\r\nWhen using an <code>Ingress</code> resource, you also get the ability to identify callers using <a href=\"https://tailscale.com/docs/features/tailscale-serve#identity-headers\">HTTP headers</a> injected by the <code>Ingress</code> proxy.</p>\n<p><code>Ingress</code> resources only support TLS, and are only exposed over HTTPS using a <a href=\"https://tailscale.com/docs/features/magicdns\">MagicDNS</a> name and publicly\r\ntrusted certificates from Let's Encrypt. You must <a href=\"https://tailscale.com/docs/how-to/set-up-https-certificates\">enable HTTPS</a> and <a href=\"https://tailscale.com/docs/features/magicdns\">MagicDNS</a> on your\r\ntailnet.</p>\n<p>Edit the <code>Ingress</code> resource you want to expose to use the <code>Ingress</code> class <code>tailscale</code>:</p>\n<ol>\n<li>Set <code>spec.ingressClassName</code> to <code>tailscale</code>.</li>\n<li>Set <code>tls.hosts</code> to the desired host name of the Tailscale node. Only the first label is used. Refer to <a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/customize#custom-machine-names\">custom machine names</a> for more information.</li>\n</ol>\n<p>For example, to expose an <code>Ingress</code> resource <code>nginx</code> to your tailnet:</p>\n<pre><code class=\"language-yaml\">apiVersion: networking.k8s.io/v1\r\nkind: Ingress\r\nmetadata:\r\n  name: nginx\r\nspec:\r\n  defaultBackend:\r\n    service:\r\n      name: nginx\r\n      port:\r\n        number: 80\r\n  ingressClassName: tailscale\r\n  tls:\r\n    - hosts:\r\n        - nginx\n</code></pre>\n<p>The backend is HTTP by default. To use HTTPS on the backend, either set the port name to <code>https</code> or the port number to <code>443</code>:</p>\n<pre><code class=\"language-yaml\">apiVersion: networking.k8s.io/v1\r\nkind: Ingress\r\nmetadata:\r\n  name: nginx\r\nspec:\r\n  defaultBackend:\r\n    service:\r\n      name: nginx\r\n      port:\r\n        name: https\r\n  ingressClassName: tailscale\r\n---\r\napiVersion: v1\r\nkind: Service\r\nmetadata:\r\n  name: nginx\r\nspec:\r\n  ports:\r\n    - name: https\r\n      port: 443\r\n      targetPort: 443\r\n  type: ClusterIP\n</code></pre>\n<p>A single <code>Ingress</code> resource can be used to front multiple backend <code>Services</code>:</p>\n<pre><code class=\"language-yaml\">apiVersion: networking.k8s.io/v1\r\nkind: Ingress\r\nmetadata:\r\n  name: ingress\r\nspec:\r\n  ingressClassName: tailscale\r\n  rules:\r\n    - http:\r\n        paths:\r\n          - path: /\r\n            pathType: Prefix\r\n            backend:\r\n              service:\r\n                name: ui-svc\r\n                port:\r\n                  number: 80\r\n          - path: /api\r\n            pathType: Prefix\r\n            backend:\r\n              service:\r\n                name: api-svc\r\n                port:\r\n                  number: 80\n</code></pre>\n<p>Currently the only supported <a href=\"https://kubernetes.io/docs/concepts/services-networking/ingress/#path-types\"><code>Ingress</code> path type</a> is <code>Prefix</code>. Requests for paths with other path types will be routed according to <code>Prefix</code> rules.</p>\n<p>A Tailscale <code>Ingress</code> can only be accessed on port 443.</p>\n<h2><a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress#exposing-a-service-to-the-public-internet-using-ingress-and-tailscale-funnel\">Exposing a <code>Service</code> to the public internet using <code>Ingress</code> and Tailscale Funnel</a></h2>\n<p>You can also use the Tailscale Kubernetes Operator to expose an <code>Ingress</code> resource in your Kubernetes cluster to the public internet using <a href=\"https://tailscale.com/docs/features/tailscale-funnel\">Tailscale Funnel</a>. To do so:</p>\n<ol>\n<li>Add a <code>tailscale.com/funnel: \"true\"</code> annotation:</li>\n</ol>\n<pre><code class=\"language-yaml\">apiVersion: networking.k8s.io/v1\r\nkind: Ingress\r\nmetadata:\r\n     name: funnel\r\n     annotations:\r\n       tailscale.com/funnel: \"true\"\r\nspec:\r\n     defaultBackend:\r\n       service:\r\n         name: funnel\r\n         port:\r\n           number: 80\r\n     ingressClassName: tailscale\r\n     tls:\r\n    - hosts:\r\n        - funnel\n</code></pre>\n<ol start=\"2\">\n<li>Update the access control policies for your tailnet to allow Kubernetes Operator proxy services to use Tailscale Funnel.</li>\n</ol>\n<p>Add a <a href=\"https://tailscale.com/docs/features/tailscale-funnel#requirements-and-limitations\">node attribute</a> to allow nodes created by the Kubernetes operator to use Funnel:</p>\n<pre><code class=\"language-json\">\"nodeAttrs\": [\\\r\n{\\\r\n    \"target\": [\"tag:k8s\"], // tag that Tailscale Operator uses to tag proxies; defaults to 'tag:k8s'\\\r\n    \"attr\":   [\"funnel\"],\\\r\n},\\\r\n// Additional noteAttrs as needed\\\r\n]\n</code></pre>\n<p>Note that even if your policy has the <code>funnel</code> attribute assigned to <code>autogroup:member</code> (the default), you still need to add it to the tag used by proxies because <code>autogroup:member</code> does not include tagged devices.</p>\n<h2><a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress#removing-a-service\">Removing a Service</a></h2>\n<p>Any of the following actions remove a Kubernetes <code>Service</code> you exposed from your tailnet:</p>\n<ul>\n<li>Delete the <code>Service</code> entirely.</li>\n<li>If you use the <code>tailscale.com/expose</code> annotation, remove the annotation.</li>\n<li>If you use an <code>Ingress</code> resource, delete it or change or unset <code>spec.ingressClassName</code>.</li>\n</ul>\n<p>Deleting a <code>Service</code>'s Tailscale node in the <a href=\"https://login.tailscale.com/admin/machines\">Machines</a> page does not clean up the Kubernetes state associated with that <code>Service</code>.</p>\n<h2><a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress#high-availability\">High availability</a></h2>\n<p>Tailscale versions 1.84 and later support deploying Tailscale Kubernetes Operator's ingress proxies in high availability (HA) mode, using <a href=\"https://tailscale.com/docs/features/kubernetes-operator#proxy-group\"><code>ProxyGroup</code></a> and a new Tailscale feature, <a href=\"https://tailscale.com/docs/features/tailscale-services\">Tailscale Services</a>.</p>\n<p>The HA mode lets you:</p>\n<ul>\n<li>\n<p>Expose a Kubernetes <code>Service</code> or <code>Ingress</code> resource to your tailnet through multiple active ingress proxies, to prevent downtime during proxy <code>Pod</code> restarts.</p>\n</li>\n<li>\n<p>Expose many <code>Service</code> and <code>Ingress</code> resources to your tailnet using a smaller number of proxy <code>Pod</code>s.</p>\n</li>\n</ul>\n<h3><a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress#prerequisites-1\">Prerequisites</a></h3>\n<ul>\n<li>Ensure that the OAuth client credentials <a href=\"https://tailscale.com/docs/features/kubernetes-operator#setup\">used by the Tailscale Kubernetes Operator</a> have <code>Services</code>, <code>Devices Core</code> and <code>Auth Keys</code> write scopes.</li>\n</ul>\n<p>If you are updating credentials for an existing installation, you must recreate the Kubernetes Operator's <code>Pod</code>.</p>\n<ul>\n<li>Create a <a href=\"https://tailscale.com/docs/features/kubernetes-operator#proxy-group\"><code>ProxyGroup</code></a> with <code>spec.type</code> set to <code>ingress</code>:</li>\n</ul>\n<pre><code class=\"language-yaml\">apiVersion: tailscale.com/v1alpha1\r\nkind: ProxyGroup\r\nmetadata:\r\n    name: ingress-proxies\r\nspec:\r\n    type: ingress\n</code></pre>\n<p>Refer to the <a href=\"https://github.com/tailscale/tailscale/blob/main/k8s-operator/api.md#proxygroup\"><code>ProxyGroup</code> API documentation</a> for all available configuration options.</p>\n<h3><a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress#expose-a-tailscale-ingress-in-ha-mode\">Expose a Tailscale Ingress in HA mode</a></h3>\n<ul>\n<li>\n<p><a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress#permissions\">Configure permissions</a></p>\n</li>\n<li>\n<p>Create a Tailscale <code>Ingress</code> resource that references the <code>ProxyGroup</code> you created in the previous step.\r\nThe <code>spec.tls.hosts</code> field can contain (at most) a single entry that determines the first label of the DNS name by which the <code>Ingress</code> will be exposed to the tailnet. If unset, it defaults to <code>&#x3C;ingress-name>-&#x3C;namespace></code>.</p>\n</li>\n</ul>\n<pre><code class=\"language-yaml\">apiVersion: networking.k8s.io/v1\r\nkind: Ingress\r\nmetadata:\r\n    name: nginx\r\n    annotations:\r\n      tailscale.com/proxy-group: ingress-proxies\r\nspec:\r\n    defaultBackend:\r\n      service:\r\n        name: nginx\r\n        port:\r\n          number: 80\r\n    ingressClassName: tailscale\r\n    tls:\r\n    - hosts:\r\n        - nginx\n</code></pre>\n<ul>\n<li>Wait for the <code>Ingress</code> resource to become ready.</li>\n</ul>\n<pre><code class=\"language-shell\">kubectl wait --timeout=80s  ingress nginx --for=jsonpath='{.status.loadBalancer.ingress[0].ports[0].port}'=443\n</code></pre>\n<ul>\n<li>\n<p>Ensure that your Tailscale client <a href=\"https://tailscale.com/docs/features/subnet-routers#use-your-subnet-routes-from-other-devices\">accepts routes</a>. Clients other than Linux accept routes by default.</p>\n</li>\n<li>\n<p>Access the cluster workload from a Tailscale client:</p>\n</li>\n</ul>\n<pre><code class=\"language-shell\">kubectl get ingress nginx\r\nNAME    CLASS       HOSTS   ADDRESS                  PORTS     AGE\r\nnginx tailscale   *       nginx.tailxyz.ts.net   80, 443   15s\n</code></pre>\n<pre><code class=\"language-shell\">curl https://nginx.tailxyz.ts.net\r\n...\n</code></pre>\n<h3><a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress#expose-a-tailscale-service-in-ha-mode\">Expose a Tailscale Service in HA mode</a></h3>\n<ul>\n<li>\n<p><a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress#permissions\">Configure permissions</a></p>\n</li>\n<li>\n<p>Create a Tailscale LoadBalancer <code>Service</code> that references the <code>ProxyGroup</code> you created in the previous step.\r\nYou can use the <code>tailscale.com/hostname</code> annotation to set the first label of the DNS name by which the <code>Service</code> will be exposed to the tailnet. If unset, it defaults to <code>&#x3C;service-name>-&#x3C;namespace></code>.</p>\n</li>\n</ul>\n<pre><code class=\"language-yaml\">apiVersion: v1\r\nkind: Service\r\nmetadata:\r\n    name: nginx\r\n    annotations:\r\n      tailscale.com/proxy-group: ingress-proxies\r\n      tailscale.com/hostname: nginx\r\nspec:\r\n    ports:\r\n    - name: http\r\n      port: 80\r\n      targetPort: 80\r\ntype: LoadBalancer\r\nloadBalancerClass: tailscale\n</code></pre>\n<ul>\n<li>Wait for the resources to be configured.</li>\n</ul>\n<pre><code class=\"language-shell\">kubectl wait svc nginx --for condition=TailscaleIngressSvcConfigured\n</code></pre>\n<ul>\n<li>Access the cluster workload from the tailnet.</li>\n</ul>\n<pre><code class=\"language-shell\">kubectl get svc nginx\r\nNAME    TYPE           CLUSTER-IP      EXTERNAL-IP     PORT(S)        AGE\r\nnginx LoadBalancer   10.96.194.223   100.91.19.147   80:31967/TCP   5m16s\n</code></pre>\n<pre><code class=\"language-shell\">curl http://nginx.tailxyz.ts.net\r\n...\n</code></pre>\n<p>You can expose an <a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress#annotations\">annotated Tailscale ingress Service</a> in the same way.</p>\n<h3><a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress#configure-permissions\">Configure permissions</a></h3>\n<p>By default, the <code>ProxyGroup</code> proxies are <a href=\"https://tailscale.com/docs/features/tags\">tagged</a> with the tag <code>tag:k8s</code>.\r\nYou can configure tags using the <code>.tags</code> field in the <a href=\"https://github.com/tailscale/tailscale/blob/main/k8s-operator/api.md#proxygroup\"><code>ProxyGroup</code> spec</a>.\r\nYou must ensure that the tag by which you tagged the <a href=\"https://tailscale.com/docs/features/kubernetes-operator#setup\">operator's OAuth client credentials</a> is a <a href=\"https://tailscale.com/docs/reference/syntax/policy-file#tag-owners\"><code>tagOwner</code></a> of the <code>ProxyGroup</code> device tags.</p>\n<p>Unlike with non-HA proxies, the proxy tags are not used to grant access to the cluster apps exposed using the proxies.</p>\n<p>For each HA <code>Service</code> or <code>Ingress</code> exposed on a <code>ProxyGroup</code>, the Kubernetes Operator creates a Tailscale Service.\r\nEach <code>ProxyGroup</code> proxy advertises the Tailscale Service, by configuring itself as a backend for the tailnet traffic for the Tailscale Service.\r\nThe Tailscale IP address and DNS name given to the <code>Ingress</code> or <code>Service</code> are the IP addresses and DNS name of the Tailscale Service.</p>\n<p>You can <a href=\"https://tailscale.com/docs/features/tags\">tag</a> Tailscale Service and use the tag to configure which tailnet devices can advertise the Service and which tailnet identities can access it.\r\nBy default, the Kubernetes Operator tags all Tailscale Services with a tag <code>tag:k8s</code>.\r\nYou can configure Tailscale Service tags using <code>tailscale.com/tags</code> annotation on the <code>Service</code> or <code>Ingress</code> resource.</p>\n<h4><a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress#ensure-that-proxygroup-devices-can-advertise-the-tailscale-service\">Ensure that <code>ProxyGroup</code> devices can advertise the Tailscale Service</a></h4>\n<p>To permit <code>ProxyGroup</code> devices to advertise a Tailscale Service, use the <a href=\"https://tailscale.com/docs/reference/syntax/policy-file#autoapprovers\"><code>autoApprovers</code></a> section of the tailnet policy file.</p>\n<p>For example, to let <code>ProxyGroup</code> devices with the tag <code>tag:eu-cluster</code> to advertise Tailscale Services with tag <code>tag:monitoring</code>, add the following to your tailnet policy file:</p>\n<pre><code class=\"language-json\">\"autoApprovers\": {\r\n\"services\": {\r\n    \"tag:monitoring\": [\"tag:eu-cluster\"],\r\n},\r\n}\n</code></pre>\n<h4><a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress#configure-access\">Configure access</a></h4>\n<p>You can use Tailscale Service tags to control access to it.</p>\n<p>For example, to let the user group <code>group:eng</code> to access Tailscale Services with the tag <code>tag:monitoring</code> exposed on a <code>ProxyGroup</code> with the tag <code>tag:eu-cluster</code>, add the following to your tailnet policy file:</p>\n<pre><code class=\"language-json\">\"grants\": [\\\r\n{\\\r\n    \"src\": [\"group:eng\"],\\\r\n    \"dst\": [\"tag:monitoring\"],\\\r\n    \"ip\":  [\"*\"],\\\r\n},\\\r\n{\\\r\n    \"src\": [\"group:eng\"],\\\r\n    \"dst\": [\"tag:eu-cluster:*\"],\\\r\n    \"ip\":  [\"icmp:*\"],\\\r\n},\\\r\n]\n</code></pre>\n<p>The requirement to permit access to the <code>ProxyGroup</code> devices to access Tailscale Services is a temporary limitation.</p>\n<h2><a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress#ipv6-support\">IPv6 support</a></h2>\n<h3><a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress#ingress\">Ingress</a></h3>\n<p>To proxy traffic to IPv6 backends, you might need to disable IPv4 tailnet addresses for the proxy tailnet nodes.</p>\n<p>You need to disable IPv4 tailnet addresses for:</p>\n<ul>\n<li>\n<p>Proxies used to expose cluster workloads using <a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress\">Tailscale ingress <code>Service</code>s</a> if they are running in a cluster that allocates IPv6 addresses to <code>Service</code>s.</p>\n</li>\n<li>\n<p>Proxies used to expose cloud services using <a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/cloud-services\">Tailscale ExternalName <code>Service</code>s</a> if the cloud services have IPv6 addresses.</p>\n</li>\n</ul>\n<p>You can disable tailnet IPv4 addresses for a specific <a href=\"https://tailscale.com/docs/features/tags\">tag</a> using a <code>disable-ipv4</code> <a href=\"https://tailscale.com/docs/reference/syntax/policy-file#nodeattrs\">node attribute</a>.</p>\n<p>The following node attributes configuration example disables IPv4 addresses for all nodes tagged with <code>tag:k8s</code>:</p>\n<pre><code class=\"language-json\">\"nodeAttrs\": [\\\r\n{\\\r\n    \"target\": [\"tag:k8s\"],\\\r\n    \"attr\": [\\\r\n      \"disable-ipv4\",\\\r\n    ],\\\r\n},\\\r\n]\n</code></pre>\n<p>Tailnet IPv6 connectivity does not depend on host support for IPv6, so you can disable IPv4 addresses for nodes running on hosts that do not support IPv6.</p>\n<p>Similarly, tailnet clients can connect to proxies with only tailnet IPv6 addresses even if they aren't running on hosts with IPv6 support.</p>\n<h2><a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress#customization\">Customization</a></h2>\n<p><a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/customize\">Customize the operator and resources it manages</a>.</p>\n<h2><a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress#troubleshooting\">Troubleshooting</a></h2>\n<p><a href=\"https://tailscale.com/docs/reference/troubleshooting/containers/kubernetes-operator\">Troubleshoot the operator and resources it manages</a>.</p>\n<h2><a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/cluster-ingress#limitations\">Limitations</a></h2>\n<ul>\n<li>Tags are only considered during initial provisioning. That is, editing <code>tailscale.com/tags</code> on an already exposed <code>Service</code> doesn't update the tags until you clean up and re-expose the <code>Service</code>.</li>\n<li>The requested machine name is only considered during initial provisioning. That is, editing <code>tailscale.com/hostname</code> on an already exposed <code>Service</code> doesn't update the machine name until you clean up and re-expose the <code>Service</code>.</li>\n<li>Cluster-ingress using Kubernetes <code>Ingress</code> resource requires TLS certificates. Currently, the certificates are provisioned on the first connect. This means that the first connection might be slow or even time out.</li>\n</ul>\n<p><img src=\"https://cdn.brandfetch.io/tailscale.com/fallback/lettermark/theme/dark/h/256/w/256/icon?c=1bfwsmEH20zzEfSNTed\" alt=\"Project Logo\"></p>\n<p>Ask AI</p>\n<p>reCAPTCHA</p>\n<p>Recaptcha requires verification.</p>\n<p>protected by <strong>reCAPTCHA</strong></p>\n"}