# What Is the Exec Service Type in Tailcat? A Complete Technical Guide

> Learn about Tailcat's exec service type. This guide details how it runs a command for each TCP connection, using the connection as stdin/stdout in an inetd style.

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

---

**The exec service type in Tailcat launches a user-specified command for every incoming TCP connection, treating the connection as the process's standard input and output in an inetd-style execution model.**

The exec service is one of the built-in service types available when running Tailcat as a server in the tailscale/tailcat repository. This service transforms Tailcat into a lightweight, connection-per-process daemon that automatically handles peer authentication and environment enrichment through WireGuard-based tunnels. Unlike static file serving or SSH access, the exec service spawns ephemeral processes that interact directly with network clients.

## Activating the Exec Service

You can enable the exec service through two mutually supportive mechanisms defined in [`cmd/tailcat/tailcat.go`](https://github.com/tailscale/tailcat/blob/main/cmd/tailcat/tailcat.go).

**Method 1: Service Flag**

Add the service name `exec` to the `--serve` flag. The flag description is implemented at [line 448](https://github.com/tailscale/tailcat/blob/main/cmd/tailcat/tailcat.go#L448).

**Method 2: Command Specification**

Specify a command after the `--` separator on the command line. The `splitExecArgs` function extracts the command and its arguments, storing them in the `execArgs` variable ([lines 14‑26](https://github.com/tailscale/tailcat/blob/main/cmd/tailcat/tailcat.go#L14-L26)).

When `execArgs` is non-nil, the server automatically adds the `"exec"` service to its service set ([lines 46‑48](https://github.com/tailscale/tailcat/blob/main/cmd/tailcat/tailcat.go#L46-L48)). The server validates that a command is actually provided and that the exec service does not conflict with SSH or Files services, aborting if these constraints are violated ([lines 49‑51](https://github.com/tailscale/tailcat/blob/main/cmd/tailcat/tailcat.go#L49-L51)).

## Command Parsing and Handler Creation

The exec service handler is instantiated during server construction via `Server.ExecConnHandler(execArgs)` ([lines 1449‑1451](https://github.com/tailscale/tailcat/blob/main/cmd/tailcat/tailcat.go#L1449-L1451)). This handler, implemented in [`tailcat_exec.go`](https://github.com/tailscale/tailcat/blob/main/tailcat_exec.go), manages the lifecycle of spawned processes and their attachment to network connections.

## Per-Connection Process Execution

For each incoming TCP connection, `ExecConnHandler` performs the following sequence:

1. **Process Creation**: Constructs the command using `exec.Command(argv[0], argv[1:]...)` ([line 28](https://github.com/tailscale/tailcat/blob/main/tailcat_exec.go#L28)).

2. **Environment Setup**: Extends the process environment with peer-specific variables generated by `Server.PeerEnv` ([lines 29‑30](https://github.com/tailscale/tailcat/blob/main/tailcat_exec.go#L29-L30)).

3. **I/O Wiring**: Uses `runConnCommand` ([lines 60‑99](https://github.com/tailscale/tailcat/blob/main/tailcat_exec.go#L60-L99)) to bind the connection's read side to the command's stdin and the command's stdout back to the connection.

The command runs until it exits, at which point the connection is automatically closed. Errors during execution are logged with the command name for debugging ([lines 31‑33](https://github.com/tailscale/tailcat/blob/main/tailcat_exec.go#L31-L33)).

## Environment Variables and Peer Context

Commands spawned by the exec service receive enriched environment variables that enable peer-aware applications:

- **`TAILCAT_PEER_KEY`**: The public key of the connecting Tailscale node
- **`TAILCAT_REMOTE_ADDR`**: The remote address of the peer
- **`TAILCAT_LOCAL_ADDR`**: The local address accepting the connection

These variables allow scripts to implement access control, logging, or per-peer customization without requiring separate authentication mechanisms.

## Security Constraints

The exec service is **mutually exclusive** with SSH-related services unless using forced commands intentionally. The server prevents accidental misconfigurations by validating service compatibility during startup. Additionally, the exec service requires an explicit command specification; attempting to enable the service without providing a command results in an error.

## Usage Examples

Run a fortune program for each incoming connection:

```bash
tailcat serve exec -- /usr/bin/fortune

```

Echo client data while exposing the peer's public key:

```bash
tailcat serve exec -- sh -c 'cat; echo "peer=$TAILCAT_PEER_KEY"'

```

These commands start a Tailcat server that listens on the configured port and spawns the specified program for every client connection. The program's stdin receives data from the client, stdout returns data to the client, and the environment contains authenticated peer information.

## Summary

- The exec service implements an **inetd-style** execution model where each TCP connection spawns a new process.
- Activation requires either the `exec` service flag or a command after the `--` separator, parsed by `splitExecArgs` in [`cmd/tailcat/tailcat.go`](https://github.com/tailscale/tailcat/blob/main/cmd/tailcat/tailcat.go).
- Process execution is handled by `ExecConnHandler` in [`tailcat_exec.go`](https://github.com/tailscale/tailcat/blob/main/tailcat_exec.go), which uses `runConnCommand` for I/O multiplexing.
- Child processes receive **peer authentication data** through environment variables (`TAILCAT_PEER_KEY`, `TAILCAT_REMOTE_ADDR`, `TAILCAT_LOCAL_ADDR`).
- The service is **incompatible** with SSH and Files services to prevent security conflicts.
- End-to-end tests in [`cmd/tailcat/exec_e2e_test.go`](https://github.com/tailscale/tailcat/blob/main/cmd/tailcat/exec_e2e_test.go) verify correct command execution and error handling.

## Frequently Asked Questions

### How do I enable the exec service in Tailcat?

You can enable the exec service by appending `exec` to the `--serve` flag or by providing a command after the `--` separator on the command line. If you use the `--` method, Tailcat automatically adds the exec service to the active service set and validates that a command is present.

### What environment variables are available to commands spawned by the exec service?

Commands receive `TAILCAT_PEER_KEY` (the connecting node's public key), `TAILCAT_REMOTE_ADDR` (the peer's network address), and `TAILCAT_LOCAL_ADDR` (the local listening address). These are injected by `Server.PeerEnv` and allow scripts to identify and authorize connecting peers.

### Can I use the exec service alongside Tailcat's SSH service?

No, the exec service is mutually exclusive with SSH-related services unless you explicitly intend to use forced commands. The server validates service compatibility during startup and aborts if conflicting services are detected, preventing accidental security misconfigurations.

### How does Tailcat handle the lifecycle of exec processes?

Tailcat creates a new process for each incoming connection using `exec.Command`. The process runs until it exits naturally, with its stdin and stdout wired to the network connection via `runConnCommand`. Once the process terminates, Tailcat closes the corresponding connection and logs any execution errors.