Standalone CLI client for creating secure tunnels through ForTunnels. Expose local HTTP/HTTPS, TCP, and UDP services to the internet in seconds.
ForTunnels Client is a command-line tool that connects to the ForTunnels server at https://fortunnels.ru and creates a public URL for your local service.
- HTTP/HTTPS tunnels - forward web services with automatic routing
- TCP tunnels - forward any TCP protocol (SSH, databases, etc.)
- UDP tunnels - support for UDP protocols (DNS, syslog, etc.)
- Multiple transports - WebSocket/smux, QUIC, DTLS
- Encryption - optional client-side stream encryption (PSK)
- Monitoring - tunnel status via WebSocket
- Cross-platform - Linux, macOS, Windows
- Go 1.25.7 or newer (toolchain
go1.26.5; seego.mod) make(optional, for convenience targets)
git clone https://github.com/fortunnels/client.git
cd client
go build -o ./bin/client ./cmd/client
# On Windows: go build -o ./bin/client.msi ./cmd/clientThe released CLI product name is fortunnels (see make release artifacts). A local make build writes ./bin/client (or ./bin/client.msi on Windows).
Or via make:
make build # binary in ./bin/client (includes checks)
make build-fast # without tests/coverage
make format # rewrite sources (gofumpt + goimports)On Windows (or when cross-compiling with GOOS=windows), the binary is produced as bin/client.msi instead of bin/client.
The BIN_DIR environment variable controls the output directory:
make build BIN_DIR=../binBinaries for common platforms are available on the ForTunnels downloads page.
On Windows, you can install from the Chocolatey Community Repository when the package is available:
choco install fortunnelsOn macOS and Linux, a Homebrew tap is published separately; see the main ForTunnels site.
Create a tunnel to a local web server on port 8000:
./bin/client 8000After the tunnel is created you will receive a public URL like https://fortunnels.ru/t/{tunnel-id}/.
Host-based routing is also available, for example: https://{subdomain}.fortunnels.ru
(where {subdomain} is a random identifier, not the tunnel ID).
./bin/client -local 127.0.0.1:8443 -protocol httpsExpose a local TCP service (e.g. PostgreSQL, SSH) to a public endpoint:
./bin/client tcp 5433Or with explicit flags:
./bin/client -protocol tcp -local 127.0.0.1:5433The server allocates a public TCP port. Connect to the displayed tcp://host:port URL from anywhere.
Expose a local UDP service (e.g. DNS, game server):
./bin/client udp 9000./bin/client -protocol udp -udp-listen :5353 -udp-dst 127.0.0.1:53-allow-insecure-http- allow insecure HTTP for non-local addresses (not recommended)-local- local service address to forward (e.g.127.0.0.1:8000)-protocol http|https|tcp|udp- tunnel protocol-user- user identifier (for audit/quotas, default:default)-dp ws|quic|dtls- data-plane transport (default:ws)
Default: all tunnels run in blocking mode and stay active until Ctrl+C.
- Default (expose-local): Server accepts external TCP, forwards to your local backend.
-backoff-initial- reconnect backoff (sec, default: 1)-backoff-max- max reconnect backoff (sec, default: 30)
Default (expose-local): fortunnels udp <port> forwards public UDP to your local service.
Advanced reverse mode (both flags required):
-udp-listen :PORT- local UDP listen address (e.g.:5353)-udp-dst host:port- server-side UDP destination (e.g.127.0.0.1:53)
-ping-interval- WebSocket ping interval (default:30s)-ping-timeout- ping write timeout (default:10s)-smux-keepalive-interval- smux keepalive interval (default:25s)-smux-keepalive-timeout- smux keepalive timeout (default:60s)-watch- tunnel monitoring mode (subscription/polling)-watch-interval- HTTP poll interval after WS subscription (default:10s)
-encrypt- enable client-side stream encryption (PSK) over data-plane-psk- pre-shared key (required with-encrypt)-psk-file- read PSK from a file-psk-stdin- read PSK from stdin
Note: When using -encrypt, you must provide a non-empty -psk.
- When using
-login/-pass(session auth), the fallback lifecycle poller uses the same authenticated client (cookie jar) for tunnel status checks. Bearer token auth is also supported. -watchmode uses the same auth for both WebSocket subscription and HTTP fallback polling.
-login- login for server authentication-pass- password for server authentication-pass-file- read password from a file-pass-stdin- read password from stdin-token- Bearer JWT token for API authorization-token-file- read token from a file-token-stdin- read token from stdin-dp-auth-token- ready data-plane auth token (hex)-dp-auth-token-file- read data-plane token from a file-dp-auth-token-stdin- read data-plane token from stdin-dp-auth-secret- secret for computing token (HMAC-SHA256 overtunnel_id)-dp-auth-secret-file- read data-plane secret from a file-dp-auth-secret-stdin- read data-plane secret from stdin
Short forms are supported:
client 8000- HTTP tunnel to127.0.0.1:8000client http 8000- explicit protocolclient tcp 22- TCP tunnel to127.0.0.1:22
- All connections to the server use HTTPS (TLS 1.2+)
- Server certificate validation is enabled by default
- Auto-configuration is used for local development (
localhost/127.0.0.1) - HTTP for non-local addresses is blocked without
-allow-insecure-http
The client supports optional encryption over the data-plane:
- XChaCha20-Poly1305 (AEAD)
- Key derived from PSK and tunnel ID:
SHA256(PSK || tunnel_id) - Enable:
-encrypt -psk "your-secret-key"
Recommendations:
- Use long random keys (at least 32 bytes)
- Do not reuse a PSK across tunnels
- Store PSK in environment variables, files, or secret managers
- Authentication via login/password or JWT token
- Tokens are passed via
Authorization: Bearer <token> - Data-plane supports additional HMAC-based authentication
- Prefer
*-fileor*-stdinfor sensitive data
Test cloud webhooks against a local server:
./bin/client -local 127.0.0.1:3000 -protocol httpUse the public URL to configure the webhook.
./bin/client tcp 22./bin/client udp 53Advanced reverse mode:
./bin/client -protocol udp -udp-listen :5353 -udp-dst 127.0.0.1:53./bin/client 8000 -watch -watch-interval 5s./bin/client 8000 -encrypt -psk "$(openssl rand -hex 32)"./bin/client tcp 22 -dp quic./bin/client -protocol udp -dp dtls -udp-listen :5353 -udp-dst 127.0.0.1:53Problem: Unable to connect to server
Fixes:
- Check that the ForTunnels server is reachable:
curl https://fortunnels.ru/api/health - Check network settings and firewall
Problem: Failed to create tunnel
Fixes:
- Check authentication: use
-login/-passor-token - Ensure the local service is reachable:
curl http://127.0.0.1:8000 - Check address format: must be
host:port(e.g.127.0.0.1:8000)
Problem: empty PSK
Fix: When using -encrypt, provide a non-empty -psk:
./bin/client 8000 -encrypt -psk "your-secret-key-here"Problem: connections drop
Fixes:
- Increase timeouts:
-ping-timeout 30s,-smux-keepalive-timeout 120s - Check internet stability
- Use
-watchto monitor tunnel state
Problem: UDP packets do not pass
Fixes:
- Ensure both flags are set:
-udp-listenand-udp-dst - Check that UDP is enabled on the server
- Try a different transport:
-dp quicor-dp dtls
client/
|-- cmd/client/ # CLI entrypoint
|-- internal/
| |-- auth/ # Authentication
| |-- config/ # Configuration and validation
| |-- control/ # Control-plane operations
| |-- dataplane/ # Data-plane transports (WS, QUIC, DTLS)
| |-- security/ # Encryption (PSK)
| `-- support/ # Utilities and error handling
|-- shared/
| `-- wsconn/ # WebSocket adapter for smux
|-- Makefile # Build/test/release targets
|-- go.mod # Go module
`-- .golangci.yml # Linter settings
Project uses custom linter configuration (.golangci.yml) tuned for CLI apps:
make format- rewrite sources (gofumpt -w,goimports -w)make format-check- read-only format gate (fails if sources need formatting)make check- full checks (format-check, go vet, tests, golangci-lint, govulncheck, staticcheck, ineffassign, misspell, gocyclo)- All checks run automatically on
make build(checks do not rewrite sources)
Strictness highlights:
- Cyclomatic complexity
gocyclolimited to 15 (gocyclo -over 15) funlenmax 80 lines and 60 statements per functionlllmax line length 200- Duplicate finder (
dupl) triggers at 50 tokens - Any
//nolintmust include an explanation and list of linters
- Linux (amd64, arm64)
- macOS (amd64, arm64)
- Windows (amd64, arm64, x86)
Proprietary License - see LICENSE for details. This software is proprietary and may not be reused in other projects.
- ForTunnels - official site