# How to Transfer Files Using Tailcat: A Complete Guide to SFTP File Sharing

> Learn how to transfer files using Tailcat with its built-in SFTP subsystem. This guide covers secure file sharing and setting up access modes for local directories.

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

---

**Tailcat enables secure file transfers through a built-in SFTP subsystem that exposes local directories with configurable access modes when started with the `-ssh` flag and a `FileService` configuration.**

The `tailscale/tailcat` repository provides a lightweight mechanism for transferring files over encrypted SSH connections without requiring a full shell environment. By leveraging the SFTP implementation in [`tailcat_sftp.go`](https://github.com/tailscale/tailcat/blob/main/tailcat_sftp.go) and configuration structures defined in [`tailcat_files.go`](https://github.com/tailscale/tailcat/blob/main/tailcat_files.go), you can expose specific directories to remote clients with granular permission controls ranging from read-only access to write-only drop boxes.

## Understanding Tailcat's SFTP Architecture

Tailcat's file transfer capability centers on three core components that work together to provide secure, scoped access to the filesystem.

### Core Configuration Structures

The `FileService` struct defined in [`tailcat_files.go`](https://github.com/tailscale/tailcat/blob/main/tailcat_files.go) serves as the foundation for all file transfers. This structure binds a local directory path to an access mode, telling Tailcat exactly what to expose and how clients may interact with it. The `FileServeMode` type enumerates four distinct permission levels: `FileServeRO` (read-only), `FileServeRW` (read-write), `FileServeWO` (write-only), and `FileServeWOPlus` (write-only with directory creation privileges).

The `SSHOptions` struct connects these file services to the SSH server. When you populate the `Files` field with a `FileService` instance and provide authorized public keys via `AuthorizedKeys`, Tailcat initializes an SFTP subsystem rather than a standard shell session. According to the source code in [`cmd/tailcat/tailcat.go`](https://github.com/tailscale/tailcat/blob/main/cmd/tailcat/tailcat.go), the CLI parses flags such as `-ssh-files` and `-ssh-files-mode` to populate these structures automatically.

### SFTP Subsystem Implementation

The actual protocol handling resides in [`tailcat_sftp.go`](https://github.com/tailscale/tailcat/blob/main/tailcat_sftp.go), where the `Server.SSHConnHandler` manages incoming connections. This implementation translates standard SFTP protocol calls—including `Open`, `Read`, `Write`, `Remove`, and `Rename`—into filesystem operations on the rooted directory specified in your `FileService`. The server enforces your selected `FileServeMode` at the protocol level, ensuring clients cannot execute unauthorized operations even if they attempt to circumvent client-side restrictions.

## Configuring File Transfer Access Modes

Tailcat provides four distinct access modes that determine what actions remote users can perform on the exposed directory.

### Read-Only Mode (ro)

Use `FileServeRO` when you need to distribute files without allowing modifications. This mode permits listing directories and downloading files while blocking all write operations.

Start a read-only server using the CLI:

```bash
tailcat -listen :2222 \
  -ssh \
  -ssh-files /home/user/public \
  -ssh-files-mode ro \
  -ssh-authorized-keys ~/.ssh/id_rsa.pub

```

Clients connecting to port 2222 can browse `/home/user/public` and download files, but cannot upload, delete, or modify anything.

### Read-Write Mode (rw)

The `FileServeRW` mode provides full filesystem access within the specified directory, allowing clients to upload, download, move, remove, and list files.

```bash
tailcat -listen :2222 \
  -ssh \
  -ssh-files /var/share \
  -ssh-files-mode rw \
  -ssh-authorized-keys ./keys.txt

```

This configuration creates a shared workspace where authorized users can manage files bidirectionally.

### Write-Only Drop Box (wo)

For scenarios requiring anonymous submission without disclosure of existing contents, `FileServeWO` implements a true drop box. Clients may upload files but cannot list the directory or download existing content.

```bash
tailcat -listen :2222 \
  -ssh \
  -ssh-files /tmp/dropbox \
  -ssh-files-mode wo \
  -ssh-authorized-keys ./keys.txt

```

This mode is ideal for collecting sensitive submissions where submitters should not see other users' files.

### Write-Only with Directory Creation (woplus)

The `FileServeWOPlus` variant extends write-only access by allowing clients to create subdirectories, useful when organizing incoming files by date or username while maintaining content secrecy.

## Step-by-Step File Transfer Setup

### Starting the Server

The CLI entry point in [`cmd/tailcat/tailcat.go`](https://github.com/tailscale/tailcat/blob/main/cmd/tailcat/tailcat.go) handles flag parsing to configure the SFTP subsystem. The `-ssh` flag enables the SSH listener, while `-ssh-files` specifies the root directory to expose. The `-ssh-files-mode` flag accepts `ro`, `rw`, `wo`, or `woplus` corresponding to the `FileServeMode` constants.

When the server initializes, it creates an SSH server instance that presents the SFTP subsystem backed by your specified directory. The server only accepts connections from clients presenting private keys matching the public keys listed in the file provided to `-ssh-authorized-keys`.

### Connecting via Standard SFTP Clients

Once the server runs on your chosen port (commonly 2222), any standard SFTP client can connect. Tailcat does not enforce specific usernames—authentication relies entirely on public key verification.

Connect using the OpenSSH `sftp` command:

```bash
sftp -P 2222 user@hostname
sftp> put localfile.txt
sftp> get remotefile.txt
sftp> ls

```

Graphical clients like FileZilla or Cyberduck also work; configure them to use SFTP protocol with your private key for authentication, leaving the password field empty.

### Programmatic Configuration in Go

For applications embedding Tailcat directly, instantiate the same structures used by the CLI:

```go
package main

import (
	"log"

	"tailscale.com/tailcat"
)

func main() {
	// Create a file service exposing "./shared" as read-write.
	fs := &tailcat.FileService{
		Dir:  "./shared",
		Mode: tailcat.FileServeRW,
	}

	// SSH options, including a public key for authentication.
	opts := tailcat.SSHOptions{
		AuthorizedKeys: []string{`ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQD... user@example.com`},
		Files:          fs,
	}

	// Start the SSH listener on port 2222.
	srv := &tailcat.Server{}
	if err := srv.StartSSH(":2222", opts); err != nil {
		log.Fatalf("SSH server failed: %v", err)
	}
	select {} // keep running
}

```

This approach gives you direct control over `FileService` and `SSHOptions` without parsing CLI flags, useful when integrating Tailcat's file transfer capabilities into larger Go applications.

## Summary

- **Tailcat provides built-in SFTP functionality** through the `-ssh` flag and `FileService` configuration, eliminating the need for separate file server software.
- **Four access modes** (`ro`, `rw`, `wo`, `woplus`) defined in [`tailcat_files.go`](https://github.com/tailscale/tailcat/blob/main/tailcat_files.go) let you precisely control whether clients can read, write, or only upload files.
- **Authentication uses standard SSH public keys** specified via `-ssh-authorized-keys`, with the server implemented in [`tailcat_sftp.go`](https://github.com/tailscale/tailcat/blob/main/tailcat_sftp.go) enforcing permissions at the protocol level.
- **Any SFTP client works** once the server starts, including command-line `sftp`, `scp`, FileZilla, or custom Go applications using the `tailcat` package directly.
- **Files are served from a rooted directory**, meaning clients cannot access paths outside the specified `Dir`, ensuring filesystem isolation.

## Frequently Asked Questions

### What SFTP clients are compatible with Tailcat?

Any client implementing the standard SFTP protocol version 3 or higher can connect to Tailcat. This includes OpenSSH's `sftp` and `scp` commands, PuTTY's PSFTP, FileZilla, Cyberduck, and WinSCP. Because Tailcat presents a standard SFTP subsystem through [`tailcat_sftp.go`](https://github.com/tailscale/tailcat/blob/main/tailcat_sftp.go), client compatibility matches that of OpenSSH servers.

### How does Tailcat authenticate SFTP connections?

Tailcat uses public key authentication exclusively. The `SSHOptions.AuthorizedKeys` field accepts a slice of OpenSSH-formatted public keys. When a client connects, Tailcat verifies the presented private key against these authorized keys. Username validation is not performed—any user identifier is accepted provided the cryptographic signature matches a whitelisted key.

### Can I restrict users to specific directories?

Yes. The `FileService.Dir` field creates a **rooted directory** that confines all SFTP operations. Clients see this directory as their filesystem root and cannot traverse upward to access parent directories or absolute paths outside the specified tree. This chroot-like behavior is enforced by the path mapping logic in [`tailcat_sftp.go`](https://github.com/tailscale/tailcat/blob/main/tailcat_sftp.go) rather than relying on operating system permissions.

### What is the difference between write-only and write-only plus modes?

`FileServeWO` (write-only) allows clients to upload files and delete their own uploads, but prevents directory listing, file reading, and directory creation. `FileServeWOPlus` adds the ability to create subdirectories within the rooted path, enabling organized file submission workflows where users might create folders for their uploads while still being barred from viewing others' content or reading files back from the server.