Skip to content

Latest commit

 

History

71 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

TailSocks

Route traffic using a local SOCKS5 or HTTP proxy through a Tailscale exit node or a tailcat server.

What is TailSocks?

TailSocks creates a local SOCKS5 and/or an HTTP proxy server, automatically routing all traffic through a Tailscale exit node of your choice or a tailcat server. This gives you the flexibility to:

  • Route specific applications through your Tailscale network without affecting your entire system
  • Use different exit nodes for different applications simultaneously
  • Access your Tailnet resources from applications that support SOCKS5 or HTTP proxies
  • Bypass VPN limitations in applications that don't support traditional VPNs
  • Skip the tailnet entirely by connecting to a tailcat server instead, with no Tailscale account required

Use Cases

  • Selective routing: Route only specific applications (browsers, CLI tools, etc) through your Tailscale network
  • Testing: Test how your services behave from different network locations
  • Development: Access development resources on your Tailnet without configuring your entire system
  • Privacy: Route sensitive traffic through your home or office network
  • Multiple exit nodes: Run multiple instances with different exit nodes for different purposes

Installation

Pre-built binaries

You can download the latest version of TailSocks from the Releases page page.

Fetch the correct archive for your system and architecture, then extract the files and copy the tailsocks binary to /usr/local/bin or another folder.

Mac users: binaries are not signed by Apple and you may get a security warning when trying to run them on your Mac.

To fix this, run this command: xattr -rc path/to/tailsocks

Using Docker/Podman

You can run TailSocks as a Docker/Podman container. Container images are available for Linux and support amd64, arm64, and armv7/armhf.

# For podman, replace "docker run" with "podman run"
docker run \
  -d \
  --rm \
  -p 127.0.0.1:5040:5040 \
  -v tailsocks-state:/data \
  ghcr.io/italypaleale/tailsocks:1 \
  --socks-addr 0.0.0.0:5040 \
  --exit-node home-server

To expose the HTTP proxy too, add -p 127.0.0.1:5041:5041 and --http-addr 0.0.0.0:5041.

The container's working directory is /data, where tsnet writes its state (/data/tsnet-state) by default. Mount a volume there to persist the node identity across restarts, otherwise the node re-registers each time.

TailSocks follows semver for versioning. The command above uses the latest version in the 1.x branch. We do not publish a container image tagged "latest".

Build from source

Using go install:

go install github.com/italypaleale/tailsocks@latest

Or clone from the Git repo:

git clone https://github.com/italypaleale/tailsocks
cd tailsocks
go build -o tailsocks

Quick Start

  1. Start TailSocks with an exit node:

    tailsocks --exit-node my-exit-node

    The exit node can be specified as:

    • An IP address (e.g., 100.64.1.2)
    • A MagicDNS name (e.g., my-exit-node)
  2. Configure your application to use the SOCKS5 proxy at 127.0.0.1:5040

Your application traffic will now route through the specified Tailscale exit node.

If your application only supports HTTP proxies, start TailSocks with --http-addr 127.0.0.1:5041 and point it there instead. See HTTP proxy below.

Usage

Basic Usage

# Use a specific exit node
tailsocks --exit-node home-server

# Use a custom SOCKS5 listen address
tailsocks --exit-node home-server --socks-addr 127.0.0.1:8080

# Allow LAN access while using the exit node
tailsocks --exit-node home-server --exit-node-allow-lan-access

SOCKS5 proxy authentication

By default the SOCKS5 proxy accepts any client. Require a password with --auth-password (see Proxy authentication below for the ways it can be supplied):

tailsocks --exit-node home-server --socks-addr 0.0.0.0:5040 --auth-password my-password

Clients authenticate with the SOCKS5 username/password method (RFC 1929), using the fixed username tailsocks. Most SOCKS5 clients accept credentials embedded in the proxy URL, e.g. socks5://tailsocks:my-password@127.0.0.1:5040.

HTTP Proxy

In addition to SOCKS5, TailSocks can expose an HTTP proxy, which is what tools that read the http_proxy and https_proxy environment variables expect. It's disabled by default, and you can enable it with the --http-addr (or -p) flag:

tailsocks --exit-node home-server --http-addr 127.0.0.1:5041

Then point your tools at it:

export http_proxy=http://127.0.0.1:5041
export https_proxy=http://127.0.0.1:5041
export no_proxy=localhost,127.0.0.1

# Will use your exit node's IP
curl https://api.ipify.org

Note that https_proxy points to an http:// URL too: HTTPS destinations are reached by opening a tunnel through the proxy with the HTTP CONNECT method, then negotiating TLS end-to-end with the destination. TailSocks never terminates or inspects TLS. Proxy listeners that speak TLS themselves (https_proxy=https://…) are not supported.

Unlike SOCKS5, an HTTP proxy always resolves destination names on the proxy side, so MagicDNS names work without any extra client configuration:

curl http://internal-service.tailnet --proxy http://127.0.0.1:5041

Both proxies can run at the same time and share the same exit node. To run the HTTP proxy alone, disable SOCKS5 by setting its address to an empty value:

tailsocks --exit-node home-server --http-addr 127.0.0.1:5041 --socks-addr ''

Warning: like the SOCKS5 proxy, the HTTP proxy is not authenticated by default. Binding it to a non-loopback address (e.g. 0.0.0.0) exposes an open proxy to everyone who can reach that address, and TailSocks will log a warning when you do so unless you've configured authentication (see Proxy authentication below).

HTTP proxy authentication

Require a password to use the HTTP proxy with --auth-password (see Proxy authentication below for the ways it can be supplied):

tailsocks --exit-node home-server --http-addr 0.0.0.0:5041 --auth-password my-password

Clients authenticate with standard HTTP proxy Basic Auth, using the fixed username tailsocks, either via credentials embedded in the proxy URL:

export https_proxy=http://tailsocks:my-password@127.0.0.1:5041

Alternatively, use a Proxy-Authorization header for tools that take the proxy URL and credentials separately.

Unauthenticated or incorrectly authenticated requests receive a 407 Proxy Authentication Required response. This applies to both CONNECT tunnels and plain requests.

Proxy authentication

--auth-password sets a single password shared by both the SOCKS5 and HTTP proxies. The username is fixed as tailsocks. It accepts:

  • The password itself: --auth-password my-password
  • -, to read the password from stdin, e.g. echo -n my-password | tailsocks --exit-node home-server --auth-password -
  • @path/to/file, to read the password from a file: --auth-password @/etc/tailsocks/password

To pass it via an environment variable, expand it into the flag yourself, e.g. --auth-password "$MY_PASSWORD".

TCP Port Forwarding

TailSocks can also forward a local TCP port to a remote host, routing the traffic through the selected exit node. This allows forwarding traffic for applications that may not support SOCKS5 proxies.

Use the --tcp (or -t) flag with a rule in the form LISTEN=TARGET:

# Listen on 127.0.0.1:3900 and forward to test.com:3900 through the exit node
tailsocks --exit-node home-server --tcp 127.0.0.1:3900=test.com:3900
  • LISTEN is the local address to bind to, as host:port (e.g. 127.0.0.1:3900). Use :3900 or 0.0.0.0:3900 to listen on all interfaces.
  • TARGET is the remote address to forward to, as host:port (e.g. test.com:3900). The host may be an IP address or a DNS/MagicDNS name, which is resolved through Tailscale (unless --local-dns is set).

The --tcp flag can be repeated to forward multiple ports at once:

tailsocks --exit-node home-server \
  --tcp 127.0.0.1:3900=test.com:3900 \
  --tcp 127.0.0.1:5432=db.internal:5432

Warning: forwarded ports are not authenticated. Binding to a non-loopback address (e.g. 0.0.0.0) exposes the forward to other hosts on your network, and TailSocks will log a warning when you do so.

Authentication

TailSocks will use your existing Tailscale authentication. If you're not logged in, you can provide an auth key:

# Via flag
tailsocks --exit-node home-server --authkey tskey-auth-xxxxx

# Via environment variable
export TS_AUTHKEY=tskey-auth-xxxxx
tailsocks --exit-node home-server

If there's no existing authentication state, you will see a URL to authenticate your node in the logs.

Authentication with OAuth2 client credentials

Alternatively to using auth keys, you can provide OAuth2 client credentials for the Tailscale control plane. These are long-lived credentials that can be used repeatedly to register multiple nodes, and each node does not require manual approval (however, if Tailnet Lock is enabled, you will need to sign each created node manually).

  1. Create a new OAuth2 client:

    1. Open the Trust credentials page of the Tailscale admin console. Select the Credential button, then choose OAuth.
    2. In the list of scopes, select only Auth keys with write access. This requires the name of an ACL tag that must be used for the nodes created with the OAuth2 client.
    3. Copy both the client ID and secret.
  2. Create a local file with the credentials stored in ~/.config/tailsocks/oauth2.json (%USERPROFILE%/.config/tailsocks/oauth2.json on Windows) with the client ID, client secret, and name of the tag:

    {
      "client_id": "...",
      "client_secret": "tskey-client-...",
      "tag": "tag-name"
    }

Run TailSocks with the --oauth2 (or -o) option to use OAuth2 credentials:

tailsocks --exit-node home-server --oauth2

Note: when using OAuth2 credentials, nodes are registered as ephemeral by default. To make them persistent, use --ephemeral=false:

tailsocks --exit-node home-server --oauth2 --ephemeral=false

Custom Tailscale Control Server

If you're using Headscale or another custom control server:

tailsocks --exit-node home-server --login-server https://headscale.example.com

Running without a tailnet (tailcat)

TailSocks can also route traffic through a tailcat server instead of joining a tailnet. Tailscale's data plane (WireGuard over DERP) does the work, but there is no control plane involved, so no Tailscale account or auth key. The tailcat server itself is the exit node.

Experimental. tailcat makes no stability promises about its Go API, its CLI, or its wire format, so this mode may need breaking changes to keep up. Joining a tailnet remains the default and the supported path.

On the server

Start tailcat as an exit node on the machine you want traffic to come out of, and restrict it to the client that will use it:

# Print the client's public key by starting TailSocks once (see below), then:
tailcat serve --allow="nodekey:cfb6bf...ddfd16" exit-node

tailcat prints a connection token to stdout: that token is all a client needs.

Warning: a tailcat exit node forwards TCP to any destination a client asks for, including its own LAN and its loopback interface. The token is a bearer credential: anyone holding it gets an unauthenticated route into that network. Always pass --allow with the public keys you intend to serve. There is no equivalent of Tailscale ACLs here.

On the client

tailsocks --experimental-tailcat tc0oFwWCAOtDhylzp4vlxCiTg8bka4p7BGTkDR...

TailSocks logs the public key it presents on startup, which is what goes into the server's --allow:

level=INFO msg="Connecting to tailcat server" clientPublicKey=nodekey:cfb6bf...ddfd16 keyFile=./tsnet-state/tailcat-key.json tailcatVersion=v0.0.0-20260827014119-c04c5afee401

tailcatVersion is the tailcat library TailSocks was built against. Since tailcat is experimental and does not promise a stable wire format (yet), it is the first thing to compare against the server when a handshake fails.

The SOCKS5 proxy, the HTTP proxy, and --tcp port forwards all work exactly as they do in tailnet mode.

Passing the token

The token is a bearer credential, and command-line arguments are readable by other users on most systems. --experimental-tailcat accepts four forms:

# The token itself
tailsocks --experimental-tailcat tc0oFwWCAOtDhylz...

# A file holding it
tailsocks --experimental-tailcat @/etc/tailsocks/token

# The TAILSOCKS_TAILCAT_TOKEN environment variable
TAILSOCKS_TAILCAT_TOKEN=tc0oFwWCAOtDhylz... tailsocks --experimental-tailcat -

# A DNS name whose "tailcat=" TXT record holds it, the same convention tailcat's own CLI uses
tailsocks --experimental-tailcat exit.example.com

Client identity

The server's --allow list is keyed on the client's public key, so TailSocks persists a private key and reuses it across restarts.
By default it lives at tailcat-key.json inside --state-dir, and it is created on first run.

# Keep it somewhere specific
tailsocks --experimental-tailcat @/etc/tailsocks/token --tailcat-key /etc/tailsocks/client.private.json

# Or use a throwaway identity, which the server has to allow anew every time
tailsocks --experimental-tailcat @/etc/tailsocks/token --tailcat-key new

The file format matches tailcat's own key files, so an existing one can be used directly:

tailcat genkey --client
tailsocks --experimental-tailcat @/etc/tailsocks/token --tailcat-key ~/.config/tailcat/keys/client-default.private.json

DNS

Because there is no Tailscale MagicDNS without a control plane, so names are resolved by querying a DNS server through the tunnel.
The lookup happens on the exit node's side of the connection, for both privacy and performance reasons.

# Default
tailsocks --experimental-tailcat @/etc/tailsocks/token --tailcat-dns 1.1.1.1:53

# Use the exit node's own resolver, reached through its loopback interface
tailsocks --experimental-tailcat @/etc/tailsocks/token --tailcat-dns 127.0.0.53:53

# Use a resolver on the exit node's LAN, which also resolves its private names
tailsocks --experimental-tailcat @/etc/tailsocks/token --tailcat-dns 192.168.1.1:53

# Or resolve locally instead, which leaks every hostname to your own network
tailsocks --experimental-tailcat @/etc/tailsocks/token --local-dns

--tailcat-dns takes an ip:port pair rather than a name.

Differences from tailnet mode

Tailnet mode tailcat mode
SOCKS5, HTTP proxy, TCP forwarding Yes Yes
Setup Tailscale account, auth key or OAuth2 A token
Choosing an exit node --exit-node, any node in the tailnet Implicit: the server the token points at
MagicDNS and short names Yes No (no control plane)
Remote DNS Tailnet DNS DNS through the tunnel
Reaching other peers The whole tailnet Only the server, and whatever it forwards to
Access control Tailnet ACLs, device approval, tailnet lock The server's --allow list
LAN access on the exit node --exit-node-allow-lan-access Always on, not configurable
UDP Not supported Not supported

Custom DERP

The client reaches the server through a DERP relay, then upgrades to a direct path where the network allows it. To use your own relays instead of Tailscale's public ones, point both sides at your own DERP map:

# Server
tailcat --serve=exit-node --derpmap-url https://derp.example.com/derpmap.json

# Client
tailsocks --experimental-tailcat @/etc/tailsocks/token --tailcat-derpmap-url https://derp.example.com/derpmap.json

Since tailcat v0.6.0, --derpmap-url on the server also defaults from the TAILCAT_DERPMAP_URL environment variable, so it doesn't have to be passed on the command line.

Command-Line Options

Usage of tailsocks:
      --auth-password string          Password required to use the SOCKS5 and HTTP proxies, under the fixed username 'tailsocks'. Accepts the password itself, '-' to read it from stdin, or '@path/to/file' to read it from a file. Leave unset to allow unauthenticated access.
  -k, --authkey string                Optional Tailscale auth key (or set TS_AUTHKEY env var; if omitted, loads from disk or prompts)
  -e, --ephemeral                     Make this node ephemeral (auto-cleanup on disconnect)
  -x, --exit-node string              Exit node selector: IP or MagicDNS base name (e.g. 'home-exit'). Required unless --experimental-tailcat is set.
  -l, --exit-node-allow-lan-access    Allow access to local LAN while using exit node
      --experimental-tailcat string   Experimental. Connect through a tailcat server instead of joining a tailnet. Accepts a token, a DNS name whose "tailcat=" TXT record holds one, '@path/to/file', or '-' to read the TAILSOCKS_TAILCAT_TOKEN environment variable.
  -h, --help                          Show this help message
  -n, --hostname string               Tailscale node name (hostname) (default "tailsocks")
  -p, --http-addr string              HTTP proxy listen address (e.g. '127.0.0.1:5041'). Disabled when empty.
      --local-dns                     Use local DNS resolver instead of resolving DNS through the tunnel
  -c, --login-server string           Optional control server URL (e.g. https://controlplane.tld for Headscale)
  -o, --oauth2                        Use OAuth2 credentials for authentication. When set, node is ephemeral by default.
  -a, --socks-addr string             SOCKS5 listen address. Set to an empty value to disable the SOCKS5 proxy. (default "127.0.0.1:5040")
  -s, --state-dir string              Directory to store tsnet state, or the tailcat client identity in tailcat mode (default "./tsnet-state")
      --tailcat-derpmap-url string    URL of the DERP map used to reach the tailcat server. Defaults to tailcat's own.
      --tailcat-dns string            DNS server to query through the tailcat tunnel, as 'ip:port', so names resolve on the exit node's side. Ignored when --local-dns is set. (default "1.1.1.1:53")
      --tailcat-key string            Path to the tailcat client identity, which the server allowlists with --allow. Defaults to 'tailcat-key.json' inside --state-dir, created on first run. Use 'new' for a throwaway key.
  -t, --tcp strings                   Forward a local TCP port to a remote host through the exit node, in the form 'LISTEN=TARGET' (e.g. '127.0.0.1:3900=test.com:3900'). Can be repeated to forward multiple ports.
  -v, --version                       Show version

Configuring Applications

Web Browsers

Firefox:

  1. Settings → Network Settings → Configure how Firefox connects to the internet
  2. Select "Manual proxy configuration"
  3. SOCKS Host: 127.0.0.1, Port: 5040
  4. Select "SOCKS v5"

To use the HTTP proxy instead, fill in "HTTP Proxy" with 127.0.0.1 and port 5041, then select "Also use this proxy for HTTPS".

Chrome/Chromium:

chrome --proxy-server="socks5://127.0.0.1:5040"

# Or, using the HTTP proxy
chrome --proxy-server="http://127.0.0.1:5041"

Command-Line Tools

Many CLI tools support SOCKS5 proxies via environment variables:

# Will use your exit node's IP
curl https://api.ipify.org --proxy socks5://127.0.0.1:5040

Tools that only understand HTTP proxies can use the http_proxy and https_proxy environment variables, which many CLIs honor without any per-tool configuration:

export http_proxy=http://127.0.0.1:5041
export https_proxy=http://127.0.0.1:5041
export no_proxy=localhost,127.0.0.1

Git:

git config --global http.proxy socks5://127.0.0.1:5040

# Or, using the HTTP proxy
git config --global http.proxy http://127.0.0.1:5041

SSH:

ssh -o ProxyCommand="nc -X 5 -x 127.0.0.1:5040 %h %p" user@host

# Or, using the HTTP proxy
ssh -o ProxyCommand="nc -X connect -x 127.0.0.1:5041 %h %p" user@host

Examples

Route Firefox through your home network

# Start TailSocks with your home exit node
tailsocks --exit-node home-server

# Configure Firefox to use SOCKS5 proxy at 127.0.0.1:5040
# Now browse with your home IP address

Access internal development resources

# Start TailSocks (no exit node needed to access Tailnet)
tailsocks --exit-node office-node

# Use curl with the proxy
curl http://internal-service.tailnet --proxy socks5h://127.0.0.1:5040

Run multiple instances for different exit nodes

# Terminal 1: Route through home
tailsocks --exit-node home --socks-addr 127.0.0.1:5040 --state-dir ./state-home

# Terminal 2: Route through office
tailsocks --exit-node office --socks-addr 127.0.0.1:5041 --state-dir ./state-office

# Now configure different apps to use different proxies

Troubleshooting

TailSocks won't start:

  • Ensure the exit node name or IP is correct
  • Check that you have permission to use the exit node in your Tailscale settings
  • Verify your Tailscale authentication is valid

Traffic not routing through exit node:

  • Confirm your application is properly configured to use the SOCKS5 or HTTP proxy
  • Check that the proxy address and port match TailSocks' listen address
  • Verify the exit node is online and accessible
  • Check Tailscale ACL to ensure that your node can use the exit node (destination name is autogroup:internet)

Tailscale Magic DNS isn't working:

  • Ensure that you have configured your application to use the DNS resolver over the SOCKS5 proxy. For example, curl requires the use of socks5h:// as protocol. This does not apply to the HTTP proxy, which always resolves names on the TailSocks side
  • Ensure that Magic DNS is enabled in your Tailnet
  • Ensure that Tailsocks is not running with the --local-dns flag

Can't access LAN resources:

  • Use the --exit-node-allow-lan-access flag. In tailcat mode the exit node always reaches its own LAN, so there is nothing to enable

tailcat handshake times out:

  • If the server runs with --allow, check that the clientPublicKey TailSocks logs on startup is on its list. A server ignores clients it does not allow, so the failure looks like a timeout rather than a rejection
  • Check for a version mismatch. tailcat makes no wire-format stability promises, so a client and a server built from different commits may not be able to talk to each other. TailSocks logs the version it was built against as tailcatVersion when connecting; tailcat has no version command of its own, so read the server's with go version -m $(which tailcat) and compare the github.com/tailscale/tailcat line
  • Check that the token is current: a server started without --key generates a new identity on every run, which invalidates the previous token. Use tailcat genkey on the server for a stable one
  • Both sides need to reach the same DERP relay. If either is behind a strict egress filter, allow outbound HTTPS to the relays in the DERP map

Names don't resolve in tailcat mode:

  • The DNS server given to --tailcat-dns has to be reachable from the exit node and has to answer over TCP. Most resolvers do, but some LAN devices only listen on UDP
  • Try --local-dns to confirm the tunnel itself is working, then fix the resolver separately

License

MIT

About

Route traffic through Tailscale exit nodes using a local SOCKS5 proxy

Topics

Resources

Stars

125 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages