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
| Stage | Evidence | Failure scope |
|---|---|---|
| Node connectivity | Online state of source and destination | The problem exists before SSH. |
| Network permission | TCP port 22 allowed on the destination | The request never reaches the SSH rule. |
| SSH rule | src, dst, users, and action | The source or OS user is not allowed. |
| Check behavior | Reauthentication prompt and checkPeriod | The connection may be waiting rather than denied. |
| Local account | The OS user exists on the destination | Policy 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 check | Interpretation of this configuration | Next check |
|---|---|---|
| Alice → server → ubuntu | Both TCP 22 and the SSH rule match | Still verify check reauthentication and the actual OS account |
| Alice → server → root | This example has no root permission | Decide whether root access is needed before adding it |
| Another user → server → ubuntu | The source does not match this example | Also inspect the full policy for broader existing permissions |
| Alice → untagged server → ubuntu | The destination tag does not match | Check 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>
Related guides
Official references
- Tailscale SSH – port 22 interception, conventional sshd scope, key files, and check mode
- Tailnet policy syntax – independent network-access and SSH-rule requirements
- Tailscale grants – source, destination, and TCP port permissions
- Tailscale connectivity troubleshooting – node connectivity before SSH
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.