# How to Use Tailcat for Secure Shell (SSH) Access: Two Methods Explained

> Learn how to use Tailcat for secure SSH access with two methods. Tailcat offers WireGuard-encrypted tunnels, public-key authentication, and bearer tokens without root access.

- Repository: [Tailscale/tailcat](https://github.com/tailscale/tailcat)
- Tags: how-to-guide
- Published: 2026-09-08

---

**Tailcat provides a built-in SSH server that runs over WireGuard-encrypted tunnels, offering both public-key authentication and bearer-token access modes without requiring root privileges or host networking changes.**

Tailcat, an open-source project from the Tailscale team (`tailscale/tailcat`), ships with a complete SSH implementation that operates entirely in userspace. Unlike traditional SSH daemons that bind to host network interfaces, Tailcat's SSH server leverages the repository's WireGuard-based tunneling to provide secure shell access through encrypted, NAT-traversing connections.

## Understanding Tailcat's Built-In SSH Server

The SSH implementation resides primarily in **[`tailcat_ssh.go`](https://github.com/tailscale/tailcat/blob/main/tailcat_ssh.go)**, which contains the core logic for creating an SSH server instance and wiring it to Tailcat's `netstack` TCP listener. When a client connects, the server maps the SSH session onto a pseudo-terminal or executes forced commands. Platform-specific PTY handling is implemented in **[`tailcat_ssh_unix.go`](https://github.com/tailscale/tailcat/blob/main/tailcat_ssh_unix.go)** and **[`tailcat_ssh_windows.go`](https://github.com/tailscale/tailcat/blob/main/tailcat_ssh_windows.go)**, ensuring native terminal behavior on Linux, macOS, and Windows.

The server registers itself within Tailcat's service framework. When started via `tailcat serve ssh`, the handler attaches to `Server.OnTCP` and activates upon incoming connections to port 22 over the Tailcat tunnel.

## Method 1: Public-Key-Authenticated SSH (Recommended)

For production environments, Tailcat supports standard OpenSSH public-key authentication through the `--ssh-authorized-keys` flag. The CLI parses this flag in **[`cmd/tailcat/ssh_authorized_keys.go`](https://github.com/tailscale/tailcat/blob/main/cmd/tailcat/ssh_authorized_keys.go)**, loading keys from local files, raw OpenSSH format, or directly from GitHub user accounts.

The server creates an `ssh.ServerConfig` that requires a matching public key before opening a shell, providing defense-in-depth alongside the WireGuard tunnel encryption.

To start a public-key-authenticated server:

```bash

# Use your local authorized_keys file

tailcat serve --ssh-authorized-keys=~/.ssh/authorized_keys ssh

```

The server outputs a Tailcat address (e.g., `tc1a2b3c...`) that serves as the connection endpoint.

Connect from a client machine:

```bash
tailcat ssh <tc-address>

```

You will be prompted for your SSH key's passphrase if applicable, then receive a shell.

You can also specify multiple key sources, including GitHub users:

```bash
tailcat serve --ssh-authorized-keys=alice@github,./team-key.pub,bob@github ssh

```

## Method 2: Auth-Free SSH (No-Auth Mode)

Tailcat offers an **auth-free mode** (`no-auth-ssh`) where the secrecy of the Tailcat address itself serves as the sole credential. The address contains a WireGuard pre-shared key (PSK) that acts as a bearer capability.

Start an auth-free server:

```bash
tailcat serve no-auth-ssh

```

Connect using the printed address:

```bash
tailcat ssh tcXyZ...

```

**Warning:** According to the `tailscale/tailcat` README, this mode is equivalent to exposing a shell on the host. Anyone who discovers the address can connect, making the PSK the only protection. Share the address only over private channels and use this mode exclusively for temporary testing or trusted environments.

## Client Access Patterns

### Direct SSH Connections

The simplest client workflow uses the built-in `ssh` subcommand:

```bash

# Server side

tailcat serve ssh

# Note the address: tcAbc...

# Client side

tailcat ssh tcAbc...

```

### Port Forwarding for Standard SSH Clients

If you prefer using your system's `ssh` binary or need to integrate with existing tools, forward the remote SSH port to a local port:

```bash

# Server side

tailcat serve ssh

# Client side - forward remote port 22 to local 2222

tailcat forward tcAbc... 2222:22

# Use any SSH client

ssh -p 2222 user@localhost

```

## Implementing SSH Services in Go

Developers can embed Tailcat's SSH server directly into Go applications. The **[`tailcat_ssh.go`](https://github.com/tailscale/tailcat/blob/main/tailcat_ssh.go)** file exposes `SSHHandler()`, which registers the built-in SSH server on port 22.

Example server implementation:

```go
package main

import (
	"context"
	"log"
	"net"
	"os"

	"github.com/tailscale/tailcat"
)

func main() {
	// Initialize server with SSH handler on port 22
	s := &tailcat.Server{
		OnTCP: func(port uint16) func(net.Conn) {
			if port == 22 {
				return tailcat.SSHHandler()
			}
			return nil
		},
	}
	
	if err := s.Start(); err != nil {
		log.Fatal(err)
	}
	log.Println("Tailcat address:", s.TailcatAddr())
	select {}
}

```

Client connection via the Go library:

```go
cl := tailcat.NewClient(tailcat.Addr(os.Args[1]))
c, err := cl.DialTCPPort(context.Background(), 22)
if err != nil {
	log.Fatal(err)
}
// Use golang.org/x/crypto/ssh to negotiate session over c
_ = c // placeholder – real code would create ssh.ClientConn over c

```

## Security Architecture and Defense in Depth

Tailcat's SSH implementation provides two distinct security layers:

- **Transport Layer:** WireGuard encryption combined with the pre-shared key embedded in the Tailcat address ensures confidentiality and integrity. The address format includes the WireGuard public key and PSK.
- **Authentication Layer:** In public-key mode, the SSH server enforces OpenSSH-compatible key verification using sources parsed by **[`cmd/tailcat/ssh_authorized_keys.go`](https://github.com/tailscale/tailcat/blob/main/cmd/tailcat/ssh_authorized_keys.go)**. This prevents unauthorized access even if the address becomes public.

The auth-free mode removes the authentication layer, relying solely on the PSK's secrecy. This trade-off prioritizes convenience over granular access control.

## Summary

- Tailcat provides a userspace SSH server in **[`tailcat_ssh.go`](https://github.com/tailscale/tailcat/blob/main/tailcat_ssh.go)** that requires no root privileges or host network configuration.
- **Public-key authentication** uses `--ssh-authorized-keys` to accept connections only from authorized keys, with support for local files and GitHub accounts.
- **Auth-free mode** treats the Tailcat address as a bearer token; the WireGuard PSK embedded in the address provides the sole access control.
- The server handles PTY allocation through platform-specific implementations in **[`tailcat_ssh_unix.go`](https://github.com/tailscale/tailcat/blob/main/tailcat_ssh_unix.go)** and **[`tailcat_ssh_windows.go`](https://github.com/tailscale/tailcat/blob/main/tailcat_ssh_windows.go)**.
- Clients can connect via `tailcat ssh`, port forwarding, or programmatically using the Go library's `DialTCPPort` method.

## Frequently Asked Questions

### Is Tailcat SSH suitable for production environments?

Yes, when using public-key authentication. The **[`cmd/tailcat/ssh_authorized_keys.go`](https://github.com/tailscale/tailcat/blob/main/cmd/tailcat/ssh_authorized_keys.go)** implementation validates keys against OpenSSH standards, and the WireGuard tunnel provides transport security. However, the auth-free mode should be restricted to development or highly trusted networks due to its reliance on address secrecy alone.

### How does Tailcat SSH differ from running OpenSSH over Tailscale?

Tailcat's SSH implementation operates entirely in userspace through the repository's `netstack`, eliminating the need for host networking privileges or separate daemon configuration. Unlike OpenSSH, which binds to system ports and requires root access for port 22, Tailcat's server runs as a standard user process while still providing encrypted tunneling via WireGuard.

### Can I use my existing OpenSSH authorized_keys files?

Absolutely. The `--ssh-authorized-keys` flag accepts standard OpenSSH `authorized_keys` file formats. The parser in **[`tailcat_ssh_keys.go`](https://github.com/tailscale/tailcat/blob/main/tailcat_ssh_keys.go)** (referenced by the CLI) processes these files identically to OpenSSH, ensuring compatibility with existing key infrastructure.

### What platforms support Tailcat SSH?

All major platforms are supported through conditional compilation. The core logic in **[`tailcat_ssh.go`](https://github.com/tailscale/tailcat/blob/main/tailcat_ssh.go)** handles protocol operations, while **[`tailcat_ssh_unix.go`](https://github.com/tailscale/tailcat/blob/main/tailcat_ssh_unix.go)** provides POSIX PTY support and **[`tailcat_ssh_windows.go`](https://github.com/tailscale/tailcat/blob/main/tailcat_ssh_windows.go)** manages Windows console handles. This architecture ensures functional parity across Linux, macOS, and Windows without platform-specific configuration.