Mesh VPNs such as Tailscale, ZeroTier and self-hosted Headscale make it easy to connect laptops, servers, containers and home labs into one private network, and just as easy to create flat, unaudited access that bypasses every other control. A tailnet is quick to start, often begins as a team's side project, and then quietly carries production SSH, database access and exposed web services. Its security depends on a handful of settings: the access policy, how devices and machines are authenticated, key expiry, what is exposed to the internet, and who can change the policy. This guide explains how mesh VPNs work, the risks specific to them, and how to harden and monitor a tailnet, with Tailscale as the main worked example and notes on ZeroTier and Headscale. It is for network, platform and security teams who run or inherit a mesh VPN. For the wider access model see the ZTNA Guide.
Key takeaways
- The policy file is the firewall. In a mesh VPN every device can potentially reach every other device directly, so the tailnet access policy (grants or ACLs) is the only thing standing between a compromised laptop and your servers.
- Machines need machine identities. Tailscale tags give servers and automation an identity separate from any person; untagged servers stay tied to the user who joined them and break when that person leaves.
- Auth keys are NHI credentials. A reusable auth key lets anyone join devices to your network; Tailscale's own documentation warns "Be very careful with reusable keys!" Treat them like any other privileged secret.
- Exposure features need explicit control. Funnel publishes services to the public internet and, by default, any member can enable it. Restrict the
funnelnode attribute, subnet routes and exit nodes to named groups or tags. - Do not fully trust the coordination server. The control plane distributes keys. Tailnet Lock (Tailscale) and controller-signed credentials (ZeroTier) reduce what a compromised or malicious control plane can do.
How mesh VPNs work and why they are different
A traditional VPN concentrates traffic through a gateway that can be firewalled and logged. A mesh VPN builds direct encrypted tunnels between peers. Tailscale uses WireGuard for the data plane, with a coordination server that distributes public node keys, assigns addresses and pushes policy; traffic flows peer to peer where possible and through relays where not. Headscale is an open source, self-hosted implementation of the Tailscale control server; its README describes it as aimed at a single tailnet for personal use or small open source organisations, and notes it is not associated with Tailscale Inc. ZeroTier uses its own protocol with a network controller that authorises members and signs credentials.
| Aspect | Tailscale | Headscale (self-hosted control) | ZeroTier |
|---|---|---|---|
| Data plane | WireGuard | Tailscale clients over WireGuard | ZeroTier protocol, virtual Layer 2 |
| Control plane | Vendor-hosted coordination server | Your server; you own availability and patching | Vendor-hosted Central or self-hosted controller |
| Access policy | Tailnet policy file: grants (recommended) or ACLs, deny by default | Policy file supporting a subset of Tailscale features | Flow rules; when disabled, all traffic is allowed |
| Machine identity | Tags with tagOwners | Tags, depending on version | Controller-signed tags and capabilities |
| Control plane trust reduction | Tailnet Lock | You are the control plane | Rules enforced by both sender and receiver |
| Best fit | Organisations wanting SSO-integrated managed service | Self-hosters needing full control, small scale | Layer 2 needs, mixed devices, self-hosting option |
The difference that matters for security: there is no single choke point. Once a device is on the tailnet, it can talk to anything the policy permits, from anywhere, and traffic does not pass through your perimeter firewall, proxy or network detection. Visibility has to come from the mesh VPN's own logs and from endpoint telemetry. See the WireGuard Security Guide for the underlying protocol.
Access policy: grants, ACLs, groups and tests
Tailscale's policy syntax reference states that access rules deny by default, recommends grants over ACLs (ACLs keep working but receive no new features), and supports groups, autogroups, tags, posture conditions and tests. New tailnets typically start from a permissive default policy, so the first hardening task is to replace it with explicit rules.
- Design by role, not by device: define groups for people (
group:sre,group:dev) and tags for machines (tag:prod-db,tag:ci-runner), and write grants from groups to tags on specific ports. Avoid rules fromautogroup:memberto*. - Use autogroup:self for personal devices: it lets a user reach their own devices without granting reach into other users' devices. It does not cover tagged devices.
- Write tests: the policy
testssection runs assertions whenever the policy changes, and Tailscale rejects a change if a test fails. Encode "developers cannot reach production databases" and "CI runners cannot reach HR systems" as tests so a careless edit cannot silently open them. - Use posture where available:
posturesdefine device conditions (for example a managed OS version) that grants can require, which moves the policy closer to Zero Trust device checks. - Manage the policy as code: keep the policy file in Git, require review, and apply it through CI with the vendor API or a GitOps action, so every change has an author and a reason. See the GitOps Security Guide.
- ZeroTier equivalent: ZeroTier documents that flow rules are disabled by default and "all traffic is allowed across the network" until they are enabled. Enable flow rules, start from a restrictive base and use controller-signed tags and capabilities for role-based segmentation.
Machine identity: tags, auth keys and key expiry
This is where a mesh VPN becomes an NHI problem. Servers, containers, CI runners and IoT devices need to join without a person signing in through a browser, which means auth keys, and they need an identity that survives staff changes, which means tags.
| Mechanism | Documented behaviour | Hardening |
|---|---|---|
| Tags | Give a device an identity separate from human users; a device has tags or a user, not both; each tag must be defined in tagOwners, which lists who may apply it | Tag every server and automation node; restrict tagOwners to a platform group or a provisioning tag |
| One-off auth key | Works for a single connection and is revoked after use | Default choice for manual or scripted server enrolment |
| Reusable auth key | Can connect multiple devices; documentation advises storing it in a key vault | Short expiry, tagged and scoped, held in a secrets manager, never in images or repos |
| Ephemeral key | Device is removed after it goes offline; recommended for containers and serverless functions | Use for all short-lived workloads to avoid stale nodes |
| Auth key expiry | 1 to 90 days, default 90; devices already joined stay connected until node key expiry | Use the shortest workable period; expiry of the key does not remove devices |
| Node key expiry | Default 180 days for user devices, configurable 1 to 180 days; disabled by default for tagged devices on first authentication | Keep expiry on for user devices; for tagged servers, compensate with inventory reviews and device removal on decommission |
| Key revocation | Revoking a key does not deauthorise devices that already used it | Remove devices from the machines list as part of incident response |
Pair auth keys with device approval where it fits: Tailscale documents that with device approval enabled, a pending device cannot send or receive traffic until an admin approves it, and pre-approved auth keys skip that step for automation. Note that Tailscale documents device approval and Tailnet Lock as mutually exclusive, so choose based on your threat model. For OAuth clients and API access tokens used to manage the tailnet itself, apply the same discipline as any automation credential. See the API Key Management Guide and the NHI Offboarding and Decommissioning Guide.
Exposure features: Serve, Funnel, subnet routers, exit nodes and Taildrop
- Serve versus Funnel: Serve shares a local service only with devices in the tailnet. Funnel exposes it to the public internet through a Funnel URL on ports 443, 8443 or 10000, over TLS only. Tailscale states that the Funnel relay cannot decrypt the traffic, but anyone with the URL can reach the service, so the service must do its own authentication. Funnel is off by default, but approving it adds a
funnelnode attribute targetingautogroup:member, meaning every member can publish services. Narrow that target to a tag or a small group. - Funnel and Serve vulnerabilities: Tailscale bulletin TS-2026-008 described a malformed HTTP request that could pin a CPU core on nodes running Serve or Funnel, reachable from the internet through Funnel; it was fixed in 1.98.9. Exposure features increase the patching urgency for every node that uses them.
- Subnet routers: a subnet router advertises an entire on-premises or cloud network into the tailnet. Restrict who may advertise routes with
autoApprovers, which the policy reference notes only apply when a route is first advertised, and write grants to specific subnets and ports rather than the whole range. - Exit nodes: an exit node carries a user's internet traffic. Use
autogroup:internetin grants to control who may use them, and treat exit nodes as egress points that need logging. - Taildrop: Tailscale's file transfer feature works only between a user's own personal devices, excludes tagged nodes, needs an admin to turn on Send Files, and works even when access policies restrict traffic between those devices. Decide whether that matches your data handling rules before enabling it.
Tailscale SSH and administrative access
Tailscale SSH moves SSH authentication and authorisation into the policy file. After tailscale set --ssh on a host, Tailscale handles SSH arriving over the tailnet on port 22 using node keys instead of distributed public keys, and rules in the ssh section define source, destination, allowed host accounts and an action.
- Use check for privileged access:
acceptadmits users already authenticated to the tailnet;checkforces re-authentication with the identity provider, which can trigger MFA. The default check period is 12 hours, configurable from one minute to 168 hours oralways. Usecheckfor root and production accounts. - Map users carefully:
autogroup:nonrootallows any non-root account, and Tailscale cautions that changing destinations without reviewing it can let any permitted source reach any non-root account. Prefer explicit account lists for production. - Remove the old path: Tailscale SSH does not change
sshd_configorauthorized_keys, so keys left on hosts still work outside the tailnet. Remove them, or restrict sshd to the tailnet interface. See the OpenSSH Server Hardening Guide. - Patch promptly: bulletins TS-2026-009 (a Linux SSH username beginning with a dash interpreted as a flag, allowing a root session against policy) and TS-2026-010 (
acceptEnvvalues passed on the command line, visible to local users and logs) show that the SSH feature is a privileged code path. Do not pass credentials throughacceptEnv.
Trust in the control plane: Tailnet Lock and self-hosting
The coordination server can add and remove nodes, so a compromised or malicious control plane could insert a node into your network. Tailnet Lock addresses this by requiring new node keys to be signed by trusted Tailnet Lock keys held on your own signing nodes; peers verify signatures before connecting.
- Requirements: at least two signing nodes, a maximum of 20, and Android devices cannot sign. Rotate Tailnet Lock keys at most once a year.
- Disablement secrets:
tailscale lock initgenerates ten disablement secrets, shown once. Store them offline like break-glass credentials; losing them can make the tailnet unrecoverable. - Edge cases: bulletin TS-2025-008 recorded that Tailnet Lock was not enforced on nodes running without a state directory, fixed in 1.90.8. Keep clients current.
- Self-hosting is not automatically safer: with Headscale you remove the vendor from the trust chain but become the control plane operator, responsible for patching, backups, availability, TLS and admin access. The project does not support running it behind reverse proxies or in containers.
Choosing and scaling: decision guidance
- Managed service with SSO: best for most organisations: identity provider integration, audit logs, policy API and vendor-maintained control plane. Accept vendor control plane trust, and add Tailnet Lock if that is a concern.
- Self-hosted control plane: suits regulated or air-gapped labs and small teams with strong operations, where third-party control is unacceptable. Budget for running it as critical infrastructure.
- Mesh VPN or ZTNA: mesh VPNs give network-layer reach between peers; ZTNA brokers application-level access with per-request policy and is usually easier to audit for user-to-application access. Many organisations use a mesh for engineering and machine-to-machine links and ZTNA for workforce application access. See the Secure Remote Access Beyond VPN Guide.
- Small teams: tag servers, replace the default policy, enforce SSO with MFA, keep auth keys one-off or ephemeral and restrict Funnel. That covers most of the risk.
- Large organisations: add policy as code with tests, SCIM-provisioned groups, device posture, log streaming to the SIEM, a decommissioning process for tagged nodes and a register of approved tailnets to catch shadow deployments.
How to implement: hardening an existing tailnet
- Inventory. Export the device list, identify untagged servers, devices with key expiry disabled, subnet routers, exit nodes, Funnel and Serve use, and all auth keys and API credentials.
- Lock down administration. Enforce SSO with MFA on the identity provider, minimise Owner and Admin roles, and require review for policy changes.
- Tag machines. Define
tagOwners, re-authenticate servers with tagged one-off keys, and remove any server still tied to a departed user. - Write the target policy in audit form first. Draft grants and tests in a branch, run the tests, and compare against flow logs (where available) to find traffic that would be blocked.
- Cut over in rings. Apply the restrictive policy to a pilot group, watch for breakage for a week, then extend. Rollback is reverting the policy commit, which takes effect within seconds.
- Restrict exposure. Narrow
funnel, subnet route and exit node permissions, move SSH tocheckrules for privileged accounts, and remove legacy SSH keys. - Clean up keys. Revoke reusable keys that are not needed, shorten expiry, move remaining keys to a secrets manager and remove stale devices.
- Decide on Tailnet Lock or device approval and roll it out with stored disablement secrets or an approval workflow.
Detection and measurement
- Configuration audit logs: Tailscale records configuration changes with timestamp, action, actor, target and a full diff for policy edits; they are on by default, cannot be disabled and are available for 90 days, and can be streamed to a SIEM. Alert on policy changes outside the pipeline, new admins, auth key creation, Funnel enablement and key expiry being disabled.
- Network flow logs: where your plan supports them, stream connection metadata and look for user devices reaching production tags, new peers talking to databases, and exit node use.
- Shadow mesh VPNs: on endpoints and servers, alert on installation of mesh VPN agents that are not approved, and on unexpected tunnel interfaces. Attackers and insiders can use such tools as remote access that bypasses the perimeter, which maps to ATT&CK T1133 External Remote Services and T1572 Protocol Tunneling.
- SSH: collect Tailscale SSH session events and session recordings for privileged hosts, and correlate with T1021.004 lateral movement detections.
| Metric | Target |
|---|---|
| Servers and automation nodes that are tagged | 100 percent |
| Policy changes applied through reviewed pipeline | 100 percent |
| Reusable auth keys active, and their maximum age | Minimal, under 30 days |
| Nodes not seen for 30 days still authorised | Zero |
| Members permitted to use Funnel | Named group only |
| Nodes on a client version with an open security bulletin | Zero after patch window |
Framework mapping
| Framework | Reference | Relevance |
|---|---|---|
| NIST SP 800-53 Rev. 5 | AC-17 Remote Access; AC-4 Information Flow Enforcement; SC-7(5) Deny by Default, Allow by Exception; IA-3 Device Identification and Authentication; AU-6 Audit Record Review | Tailnet policy, device and machine identity, logging |
| NIST CSF 2.0 | PR.AA-05; PR.IR-01; DE.CM-01 | Least privilege access policy, network protection, monitoring |
| NIST SP 800-207 | Zero Trust Architecture tenets | Per-session, identity and posture based access |
| MITRE ATT&CK | T1133 External Remote Services; T1572 Protocol Tunneling; T1021.004 SSH; T1078 Valid Accounts | Misuse of mesh VPN access and stolen keys |
| CIS Controls v8.1 | Control 12 Network Infrastructure Management; Control 13 Network Monitoring and Defense | Managing and monitoring the overlay network |
Common failure patterns
- Keeping the permissive starter policy. Every member can reach every device, so one phished laptop reaches production.
- Servers joined by a person. An engineer authenticates a production server with their own account; when they leave and are removed, the server drops off, or worse, it keeps their access rights.
- Reusable keys in images and repos. A reusable, long-lived auth key baked into a container image or committed to Git lets anyone who finds it add devices. Rotate keys found in code and remove the devices they joined. See the Leaked Credential Response Playbook.
- Funnel left open by default. Internal admin panels and development servers published to the internet because any member could enable Funnel.
- Two SSH paths. Tailscale SSH rolled out, but old authorized_keys entries left in place keep a parallel, unmanaged access path.
- Unmanaged tailnets. Teams running personal or departmental tailnets that security does not know about, bridging corporate and personal devices.
Practitioner checklist
- Replace the default policy with grants from groups to tags on specific ports, and add policy tests.
- Manage the policy file in Git with review and automated deployment.
- Tag every server and automation node and restrict
tagOwners. - Use one-off or ephemeral auth keys, store any reusable key in a secrets manager with short expiry.
- Keep node key expiry on for user devices and review tagged nodes with expiry disabled.
- Restrict the
funnelattribute, subnet route and exit node approval to named groups or tags. - Use Tailscale SSH
checkrules for privileged accounts and remove legacy SSH keys. - Enable Tailnet Lock or device approval, with disablement secrets stored offline.
- Stream configuration audit logs and flow logs to the SIEM, and alert on unapproved mesh VPN agents.
- Track security bulletins and keep clients and self-hosted control servers patched.
Standards and references
- Tailscale: Tailnet policy file syntax
- Tailscale: Auth keys
- Tailscale: Tags
- Tailscale: Tailnet Lock
- Tailscale: Funnel
- Tailscale: Tailscale SSH
- ZeroTier: Rules Engine
- Headscale: Project README
Related NHI Mgmt Group resources: ZTNA Guide · WireGuard Security Guide · Secure Remote Access Beyond VPN Guide · SSH Key Management Guide · Remote Access Identity Guide