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

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 and configuration structures defined in 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 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, 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, 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:

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.

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.

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 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:

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:

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 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 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, 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 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →