🕳 bore is a simple CLI tool for making tunnels to localhost http://bore.pub
Find a file
kfirfer a13e03372e
Implement automatic client reconnection with exponential backoff and heartbeat timeout
- Add heartbeat timeout to client control connection using server heartbeats for dead connection detection
- Introduce exponential backoff with jitter for reconnection delays
- Add CLI flags: --no-reconnect to disable auto-reconnect, --max-reconnect-delay to configure backoff cap
- Classify authentication errors as fatal (never retried), all others retried automatically
- Configure TCP keepalive on control connections for OS-level dead connection detection
- Update documentation (README.md, CLAUDE.md) to describe reconnection behavior and new flags
- Add unit tests for backoff logic and error classification
2026-02-17 14:35:36 +07:00
.github Fix actions/cache action deprecation 2025-04-14 17:52:17 -04:00
ci Bump version to 0.5.1 (#119) 2024-06-10 21:50:23 -04:00
src Implement automatic client reconnection with exponential backoff and heartbeat timeout 2026-02-17 14:35:36 +07:00
tests Use copy_bidirectional, handle half-closed TCP streams (#165) 2025-06-09 16:10:40 -04:00
.dockerignore Add a Dockerfile for building a container image (#8) 2022-04-10 23:15:15 -04:00
.gitignore Add .claude/ to .gitignore 2026-02-17 12:46:02 +07:00
AGENTS.md Add AGENTS.md and CLAUDE.md with project and development documentation 2026-02-17 12:47:56 +07:00
Cargo.lock Implement automatic client reconnection with exponential backoff and heartbeat timeout 2026-02-17 14:35:36 +07:00
Cargo.toml Implement automatic client reconnection with exponential backoff and heartbeat timeout 2026-02-17 14:35:36 +07:00
CLAUDE.md Implement automatic client reconnection with exponential backoff and heartbeat timeout 2026-02-17 14:35:36 +07:00
Dockerfile Fix Dockerfile (#151) 2025-01-10 13:31:56 -05:00
LICENSE Add LICENSE (#3) 2022-04-08 16:42:58 -04:00
README.md Implement automatic client reconnection with exponential backoff and heartbeat timeout 2026-02-17 14:35:36 +07:00
RECONNECT_MECHANISM_PLAN.md Implement automatic client reconnection with exponential backoff and heartbeat timeout 2026-02-17 14:35:36 +07:00

bore

Build status Crates.io

A modern, simple TCP tunnel in Rust that exposes local ports to a remote server, bypassing standard NAT connection firewalls. That's all it does: no more, and no less.

Video demo

# Installation (requires Rust, see alternatives below)
cargo install bore-cli

# On your local machine
bore local 8000 --to bore.pub

This will expose your local port at localhost:8000 to the public internet at bore.pub:<PORT>, where the port number is assigned randomly.

Similar to localtunnel and ngrok, except bore is intended to be a highly efficient, unopinionated tool for forwarding TCP traffic that is simple to install and easy to self-host, with no frills attached.

(bore totals about 400 lines of safe, async Rust code and is trivial to set up — just run a single binary for the client and server.)

Installation

macOS

bore is packaged as a Homebrew core formula.

brew install bore-cli

Linux

Arch Linux

bore is available in the AUR as bore.

yay -S bore # or your favorite AUR helper

Gentoo Linux

bore is available in the gentoo-zh overlay.

sudo eselect repository enable gentoo-zh
sudo emerge --sync gentoo-zh
sudo emerge net-proxy/bore

Binary Distribution

Otherwise, the easiest way to install bore is from prebuilt binaries. These are available on the releases page for macOS, Windows, and Linux. Just unzip the appropriate file for your platform and move the bore executable into a folder on your PATH.

Cargo

You also can build bore from source using Cargo, the Rust package manager. This command installs the bore binary at a user-accessible path.

cargo install bore-cli

Docker

We also publish versioned Docker images for each release. The image is built for an AMD 64-bit architecture. They're tagged with the specific version and allow you to run the statically-linked bore binary from a minimal "scratch" container.

docker run -it --init --rm --network host ekzhang/bore <ARGS>

Detailed Usage

This section describes detailed usage for the bore CLI command.

Local Forwarding

You can forward a port on your local machine by using the bore local command. This takes a positional argument, the local port to forward, as well as a mandatory --to option, which specifies the address of the remote server.

bore local 5000 --to bore.pub

You can optionally pass in a --port option to pick a specific port on the remote to expose, although the command will fail if this port is not available. Also, passing --local-host allows you to expose a different host on your local area network besides the loopback address localhost.

The full options are shown below.

Starts a local proxy to the remote server

Usage: bore local [OPTIONS] --to <TO> <LOCAL_PORT>

Arguments:
  <LOCAL_PORT>  The local port to expose [env: BORE_LOCAL_PORT=]

Options:
  -l, --local-host <HOST>                The local host to expose [default: localhost]
  -t, --to <TO>                          Address of the remote server to expose local ports to [env: BORE_SERVER=]
  -p, --port <PORT>                      Optional port on the remote server to select [default: 0]
  -s, --secret <SECRET>                  Optional secret for authentication [env: BORE_SECRET]
      --no-reconnect                     Disable automatic reconnection on connection loss
      --max-reconnect-delay <SECONDS>    Maximum delay between reconnection attempts [default: 64]
  -h, --help                             Print help

Self-Hosting

As mentioned in the startup instructions, there is a public instance of the bore server running at bore.pub. However, if you want to self-host bore on your own network, you can do so with the following command:

bore server

That's all it takes! After the server starts running at a given address, you can then update the bore local command with option --to <ADDRESS> to forward a local port to this remote server.

It's possible to specify different IP addresses for the control server and for the tunnels. This setup is useful for cases where you might want the control server to be on a private network while allowing tunnel connections over a public interface, or vice versa.

The full options for the bore server command are shown below.

Runs the remote proxy server

Usage: bore server [OPTIONS]

Options:
      --min-port <MIN_PORT>          Minimum accepted TCP port number [env: BORE_MIN_PORT=] [default: 1024]
      --max-port <MAX_PORT>          Maximum accepted TCP port number [env: BORE_MAX_PORT=] [default: 65535]
  -s, --secret <SECRET>              Optional secret for authentication [env: BORE_SECRET]
      --bind-addr <BIND_ADDR>        IP address to bind to, clients must reach this [default: 0.0.0.0]
      --bind-tunnels <BIND_TUNNELS>  IP address where tunnels will listen on, defaults to --bind-addr
  -h, --help                         Print help

Protocol

There is an implicit control port at 7835, used for creating new connections on demand. At initialization, the client sends a "Hello" message to the server on the TCP control port, asking to proxy a selected remote port. The server then responds with an acknowledgement and begins listening for external TCP connections.

Whenever the server obtains a connection on the remote port, it generates a secure UUID for that connection and sends it back to the client. The client then opens a separate TCP stream to the server and sends an "Accept" message containing the UUID on that stream. The server then proxies the two connections between each other.

For correctness reasons and to avoid memory leaks, incoming connections are only stored by the server for up to 10 seconds before being discarded if the client does not accept them.

Reconnection

By default, bore automatically reconnects to the server when the connection is lost (e.g., due to network interruptions). This makes it suitable for long-running deployments with service managers like systemd or launchd.

  • Automatic reconnection is enabled by default with exponential backoff (1s, 2s, 4s, ... up to 64s max)
  • Authentication failures (wrong secret) are never retried — the client exits immediately
  • --no-reconnect disables automatic reconnection, restoring the legacy exit-on-disconnect behavior
  • --max-reconnect-delay <SECONDS> configures the maximum backoff delay (default: 64 seconds)

Dead connections are detected via a heartbeat timeout: the server sends heartbeats every 500ms, and if no message is received within 8 seconds, the client treats the connection as dead and begins reconnecting. TCP keepalive is also configured as an additional safety net.

Authentication

On a custom deployment of bore server, you can optionally require a secret to prevent the server from being used by others. The protocol requires clients to verify possession of the secret on each TCP connection by answering random challenges in the form of HMAC codes. (This secret is only used for the initial handshake, and no further traffic is encrypted by default.)

# on the server
bore server --secret my_secret_string

# on the client
bore local <LOCAL_PORT> --to <TO> --secret my_secret_string

If a secret is not present in the arguments, bore will also attempt to read from the BORE_SECRET environment variable.

Acknowledgements

Created by Eric Zhang (@ekzhang1). Licensed under the MIT license.

The author would like to thank the contributors and maintainers of the Tokio project for making it possible to write ergonomic and efficient network services in Rust.