Tailscale SSH Policy Denied: Node vs User Rules

What the tailnet policy error identifies

If you receive tailscale: tailnet policy does not permit you to SSH to this node, the connection reached Tailscale SSH authentication before policy evaluation rejected it or found no policy. In the v1.104.1 server source, clientAuth uses rejected or noPolicy for this banner; a message containing as user uses the separate rejectedUser branch. First check the SSH rule matching the source and destination, and the requested OS user. This banner and a TCP connection timeout are different evidence.

This distinction comes from source inspection, not a reproduced tailnet incident. The complete connection also needs network permission for TCP port 22. Use the checks below to separate connectivity, SSH rules, and check-mode reauthentication; the banner alone does not identify a particular incorrect selector.

The same ssh command can reach two different servers

On a Linux node with Tailscale SSH enabled, Tailscale intercepts port 22 connections arriving from the tailnet and performs authentication and authorization. Connections to a LAN or public address can still reach the existing OpenSSH sshd. Success or failure on one path is not evidence about the other path.

Tailscale documents that it does not modify /etc/ssh/sshd_config or ~/.ssh/authorized_keys. Editing those files first can weaken a working conventional SSH path while leaving the tailnet policy error unchanged.

Separate the permissions evaluated before login

StageEvidenceFailure scope
Node connectivityOnline state of source and destinationThe problem exists before SSH.
Network permissionTCP port 22 allowed on the destinationThe request never reaches the SSH rule.
SSH rulesrc, dst, users, and actionThe source or OS user is not allowed.
Check behaviorReauthentication prompt and checkPeriodThe connection may be waiting rather than denied.
Local accountThe OS user exists on the destinationPolicy allows a login account that is absent.

Capture connection evidence before editing policy

tailscale status
tailscale ip -4
tailscale ping <DESTINATION>
ssh -vvv <USER>@<DESTINATION>

If tailscale ping also fails, distinguish its ping mode from node connectivity first. Confirm whether the destination is offline, the source is logged into another tailnet, or device approval and key state prevent connectivity. The ssh -vvv output records the resolved host, actual destination address, and the authentication stage reached.

A successful default discovery ping or --tsmp ping does not prove the TCP 22 grant or SSH user permission. --icmp tests ICMP conditions, separately from TCP 22. Compare the ping mode with the actual SSH banner.

Read the network grant and SSH rule independently

Tailscale’s policy documentation requires both network access and SSH access. A grant or legacy ACL must allow the source to reach TCP port 22 on the destination. A separate ssh rule must match the source, destination, and permitted OS users. Adding only one side does not complete the connection.

When the policy editor provides tests, use the real source and destination selectors to check the expected decision. Temporarily opening a broad wildcard neither isolates the root cause nor preserves least privilege.

A policy limited to one person, server, and OS account

This example connects Alice’s user-owned client to the existing ubuntu account on a server tagged tag:home-server. Replace the email, tag, and OS account, and separately confirm server tagging and Tailscale SSH activation. It combines a TCP grant example with an SSH rule for explanation; it is not a successful login observed in a particular tailnet.

Do not replace your entire production policy with this example. Back up the existing policy, merge only the required entries, and pass the editor’s validation. Existing broader allow rules can change the expected denials below.

{
  "tagOwners": {
    "tag:home-server": [
      "autogroup:admin"
    ]
  },
  "grants": [
    {
      "src": [
        "alice@example.com"
      ],
      "dst": [
        "tag:home-server"
      ],
      "ip": [
        "tcp:22"
      ]
    }
  ],
  "ssh": [
    {
      "action": "check",
      "src": [
        "alice@example.com"
      ],
      "dst": [
        "tag:home-server"
      ],
      "users": [
        "ubuntu"
      ],
      "checkPeriod": "12h"
    }
  ]
}
Connection to checkInterpretation of this configurationNext check
Alice → server → ubuntuBoth TCP 22 and the SSH rule matchStill verify check reauthentication and the actual OS account
Alice → server → rootThis example has no root permissionDecide whether root access is needed before adding it
Another user → server → ubuntuThe source does not match this exampleAlso inspect the full policy for broader existing permissions
Alice → untagged server → ubuntuThe destination tag does not matchCheck the actual tag and ownership on the Machines page

The table interprets the configuration in isolation. A real login also depends on connectivity, server support, the OS account, and reauthentication. A tagged source device does not fit this user-source example. Tag-to-tag access requires a separate rule, and check mode cannot be used with a tagged source.

The next check after a node or user banner

The v1.104.1 source uses the same node banner for a missing policy and a rejection. It does not by itself prove a blocked port or an incorrect tag. Inspect the policy received by the server and the source/destination selection first. For the user banner, compare the requested OS account with the rule’s users. Specifying ssh ubuntu@SERVER avoids silently requesting the client’s local username.

Server support: Synology and QNAP do not support the Tailscale SSH server. Conventional OpenSSH reached through the tailnet instead needs port 22 network access and the NAS sshd authentication checks. Do not apply the Linux Tailscale SSH activation command to a NAS unchanged.

Determine whether action is accept or check

accept permits a matching connection. check can require reauthentication with the identity provider. Tailscale documents a default check period of 12 hours, which can be changed with checkPeriod. If the browser prompt is behind another window or the source identity session expired, the SSH client can appear to pause.

For a high-risk account such as root, retaining check mode is safer. Instead of changing the action to accept during diagnosis, record whether a prompt opens and whether the same SSH command succeeds after reauthentication.

Server activation and the OS account

Start with observation only. Where supported by the installed version, prefs is a read-only diagnostic. Its full output can contain sensitive settings, so filter only RunSSH. If the command or field is absent, record unknown and consult that version’s help or management UI. A preference does not establish SSH policy permission or login success.

tailscale version
tailscale debug --help
# Only when this installed version provides the read-only prefs command:
set -o pipefail
sudo tailscale debug prefs | python3 -c 'import json,sys; p=json.load(sys.stdin); v=p.get("RunSSH"); print("RunSSH="+str(v).lower() if isinstance(v,bool) else "RunSSH=unknown"); sys.exit(0 if isinstance(v,bool) else 1)'
getent passwd USER

Only make the following change after establishing that activation is needed. Record the original RunSSH value and retain a working management session or console independent of Tailscale SSH. This changes the tailnet port-22 handling path; it is not a query.

sudo tailscale set --ssh=true

Restore only the value actually recorded beforehand; do not unconditionally set a previously enabled feature to false. The disabling command in the final section applies only when deciding not to use Tailscale SSH. Check server support for macOS variants, containers, and userspace networking. Activation and policy changes on a particular tailnet were not tested.

Verification and rollback

After editing policy, test one source that should be allowed and one that should remain denied. A single successful connection cannot reveal whether a wildcard opened too much access. Start a new session, confirm the user and destination, and separately test conventional LAN SSH.

If Tailscale SSH will not be used, disable interception on the destination with the following command. This is not a restoration of sshd_config or authorized keys because Tailscale did not change those files.

sudo tailscale set --ssh=false
tailscale status
ssh -vvv <USER>@<LAN_IP>

Official references

Earlier verification notes preserved from this article

Verification level: official documentation checked

The boundary among Tailscale network access, SSH rules, and check mode was verified from official documentation. No specific tailnet policy was reproduced, so selectors and identity mapping must be validated by its operator.

Writing and correction policy · Report an error