# mmatczuk/go-http-tunnel: a self-hosted HTTP/2 reverse tunnel built on client TLS certificates

> Go HTTP tunnel exposes a local port through a server you run yourself, multiplexing every proxied connection over one HTTP/2 connection and identifying clients by TLS certificate. It is a two-binary tool with a YAML config, an AGPL-3.0 licence and a release history that stops in 2017.

**mmatczuk/go-http-tunnel** — Fast and secure tunnels over HTTP/2

- Repository: https://github.com/mmatczuk/go-http-tunnel
- Stars: 3,327 · Forks: 313
- Language: Go
- License: AGPL-3.0
- Published: 2026-09-24 · Updated: 2026-09-24 · Language: en
- Canonical page: https://hysenlabs.com/projects/mmatczuk-go-http-tunnel

## The problem go-http-tunnel solves, and who actually needs it

The README states the premise plainly: it lets you share your localhost when you do not have a public IP. That is a narrower claim than a general VPN. The tunnel server, tunneld, runs on a host with a public address such as AWS or GCE; the tunnel client, tunnel, runs on the machine or private network that holds the service you want reachable. The README lists three common cases: hosting a game server from home, developing webhook integrations, and managing IoT devices. Those three share a shape. Something is listening on a port you cannot route to from the internet, and you want a specific hostname or TCP address pointed at it without reconfiguring the network.

The audience is therefore people who already have a server and are willing to run software on it. The README does not describe a hosted offering, a control panel or a signup flow, and the repository has no homepage field. If you want someone else to operate the public endpoint, this project is not aimed at you. If you have a small VPS and a Go toolchain, the shape fits.

## One TLS connection, many proxied streams, and a certificate as the client identity

The mechanism described in the README is short and worth reading carefully. A client opens a TLS connection to the server. The server accepts connections from known clients only, and the client is recognized by its TLS certificate ID. The server is publicly available and proxies incoming connections to the client; the connection is then proxied further inside the client's network. There is a single TCP connection between client and server, and all proxied connections are multiplexed using HTTP/2.

That single-connection design is the part with real consequences. Because every proxied stream rides one HTTP/2 connection, the client reconnects as a unit rather than per tunnel, which is why the configuration has a backoff block with interval, multiplier, max_interval and max_time, and why the client auto reconnect feature exists. The defaults given in the README are 500ms initial interval, a 1.5 multiplier, a 1m maximum interval and a 15m maximum reconnect time, with 0 meaning never stop trying.

Authentication is certificate-based on the tunnel itself, not token-based. The README says the server accepts known clients only and identifies them by TLS certificate ID, and it instructs you to generate separate client and server key pairs with openssl. The root_ca option controls whether the client validates the server: if it is empty, any server certificate is accepted. That default is convenient during setup and is a decision you should make deliberately rather than inherit. Basic authentication is a separate, per-tunnel feature for HTTP tunnels, configured as auth: user:password, and it applies to the tunneled requests rather than to the tunnel connection.

## Building tunneld and tunnel and running a first HTTP tunnel

Installation is a Go build. The README gives one command that fetches and builds both executables, or you can download the latest release instead.

```bash
$ go get -u github.com/mmatczuk/go-http-tunnel/cmd/...
```

Both binaries need TLS material. The README uses openssl to create a self-signed pair for each side, and notes that for HTTPS you should get a properly signed certificate to avoid security warnings.

```bash
$ openssl req -x509 -nodes -newkey rsa:2048 -sha256 -keyout client.key -out client.crt
$ openssl req -x509 -nodes -newkey rsa:2048 -sha256 -keyout server.key -out server.crt
```

On the public host, the README's sequence is to install tunneld, create a .tunneld directory, copy server.key and server.crt into it, and start the server. The README states this runs an HTTP server on port 80 and an HTTPS (HTTP/2) server on port 443.

```bash
$ tunneld -tlsCrt .tunneld/server.crt -tlsKey .tunneld/server.key
```

On the local machine, install tunnel, create a .tunnel directory in your project, copy client.key and client.crt into it, and create tunnel.yml there. The README's sample configuration maps localhost:8080 to webui.my-tunnel-host.com over HTTP with basic authentication, exposes a private host for ssh over TCP, and shows a sni tunnel.

```yaml
server_addr: SERVER_IP:5223
tunnels:
  webui:
    proto: http
    addr: localhost:8080
    auth: user:password
    host: webui.my-tunnel-host.com
  ssh:
    proto: tcp
    addr: 192.168.0.5:22
    remote_addr: 0.0.0.0:22
```

Start everything with the client command from the README, which reads the config file and brings up all tunnels at once.

```bash
$ tunnel -config ./tunnel/tunnel.yml start-all
```

The README also documents running tunneld under systemd on Ubuntu, with a unit file that sets ExecStart to the tunneld binary with the same -tlsCrt and -tlsKey flags, Restart=on-failure, RestartSec=30 and TimeoutSec=30, and then systemctl start and systemctl enable. That is the whole documented install path. There is no package, no container image and no installer script in the README.

## Where the design bites: DNS, UDP and the missing rollback story

The http and sni tunnel types need a hostname. The README is explicit that host requires a reserved name and a DNS CNAME, for proto=http and proto=sni. That is an operational dependency the tool cannot satisfy for you, and it means a first tunnel is not finished when the process starts; it is finished when DNS resolves. The tcp type avoids this by binding a remote_addr such as 0.0.0.0:22 on the server, which is simpler but consumes a port on the public host.

Protocol coverage is the second boundary. The README lists http, tcp and sni. There is no UDP option in the documented configuration, so anything UDP-based is out of scope for this project as described. A Golang UDP proxy is a different kind of tool.

Third, the client's reconnection behaviour is bounded by default. The README documents backoff.max_time with a default of 15m, and says setting it to 0 means never stop trying. A client that loses its server connection and exceeds the default window stops trying to reconnect. If you expect a tunnel to recover from a long outage unattended, that default is the setting to look at first.

Finally, the README does not document rollback or version pinning. The go get -u form in the installation section always takes the latest code from the module path, and the README gives no guidance on pinning a revision. The repository's newest listed release is 2.1 from 2017-11-29, while the module's go.mod declares go 1.13 and depends on golang.org/x/net v0.23.0, so the code has moved since that release. If you need reproducible builds, the release tags and the module path are not the same thing, and the README does not reconcile them.

## How it compares with SSH remote forwarding and with ngrok

The closest thing to go-http-tunnel that most engineers already have installed is SSH remote forwarding, ssh -R. Both expose a local port through a remote host, and both rely on a persistent connection between the two ends. The differences are concrete. SSH forwarding carries each forwarded connection as its own channel over the SSH transport and authenticates with SSH keys; go-http-tunnel multiplexes over HTTP/2 and authenticates the client by TLS certificate ID, per the README. SSH gives you a shell on the same connection and is present on nearly every machine; go-http-tunnel gives you a YAML file that describes several tunnels at once, including an http tunnel with basic authentication and a hostname, and a sni tunnel that routes by Server Name Indication. If your need is one port for a few minutes, ssh -R is less setup. If your need is several named HTTP services behind one public endpoint with per-tunnel credentials, the config-file model is the reason to pick this project.

Against a hosted tunnel service such as ngrok, the difference is who runs the public endpoint. A hosted service gives you a subdomain and a dashboard in exchange for trusting a third party with your traffic and accepting its limits. go-http-tunnel gives you neither a dashboard nor a subdomain; you supply the server, the certificate and the DNS record. The README's own framing supports this reading: it tells you to run tunneld on a publicly available host like AWS or GCE, and it asks you to generate your own certificates. The trade is operational work for control, and it is only worth making if you actually want the control.

## Maintenance, versioning and what AGPL-3.0 means for a tunnel you run

The repository is not archived, and the last push was on 2026-07-02. The release list tells a different story: 2.1 on 2017-11-29, v.2.0 on 2017-11-24, v.1.2 on 2017-11-20. The README's Project Status section does not promise a roadmap. It says, in capitals, that if you would like to see the project modernized you should upvote issue 142. That is a request for demand signals, not a commitment, and it is the honest way to read the project's direction. Treat the code as stable and lightly tended rather than as a product with a release cadence.

Upgrade cost follows from that. There is no documented migration path between versions, no changelog, and the installation command in the README pulls the latest module code rather than a tagged release. The go.mod pins golang.org/x/net v0.23.0 and declares go 1.13, so building from source pulls a dependency set that is newer than the 2017 releases. In practice an upgrade means rebuilding both binaries together, since tunneld and tunnel are separate executables that share the protocol package under proto/. The README does not state a protocol compatibility guarantee between versions, so upgrading one side alone is not something the documentation supports.

The licence is AGPL-3.0, per the repository's LICENSE file and the project metadata. That is the network-copyleft licence. Running tunneld on your own server to reach your own services is the ordinary use case the README describes. Distributing modified binaries, or offering a modified version as a network service to others, is where the obligations become relevant. This is not legal advice; if you plan to build a product on top of tunneld, read the licence text and talk to someone qualified.

## Conclusion

Adopt it if you want a tunnel whose server you control and you are comfortable compiling Go, generating certificates and editing tunnel.yml by hand; skip it if you need a hosted service, a web dashboard, UDP forwarding or anything the README does not describe. Before committing, verify that your client certificate is the identity the server will use, that the reserved hostname and DNS CNAME for any http or sni tunnel already resolve, and whether the project's modernization issue has moved, because the newest release in the repository is 2.1 from 2017-11-29 and the README asks users to upvote an issue rather than promising a roadmap.

## FAQ

### Is go-http-tunnel safe to use?

The README says the server accepts connections from known clients only and recognizes a client by its TLS certificate ID, and both sides require TLS certificates generated with openssl. One default to check is root_ca: the README states that if it is empty, any server certificate is accepted, so set it if you want the client to validate the server.

### What is HTTP tunneling and how does go-http-tunnel implement it?

The README describes it as a reverse tunnel based on HTTP/2: the client opens a TLS connection to the server, the server proxies incoming connections to the client, and the connection is proxied further inside the client's network. A single TCP connection carries all proxied connections, multiplexed with HTTP/2.

### Is there a free tunneling service based on go-http-tunnel?

No. The README describes two executables you run yourself, tunneld on a publicly available host such as AWS or GCE and tunnel on your local machine, and it gives no hosted endpoint or signup. The software is free to build from source under AGPL-3.0, but the server is yours to operate.

### How do I tunnel HTTP over SSH instead of using go-http-tunnel?

The README does not cover SSH forwarding; it documents its own client and server, with tunnel.yml describing http, tcp and sni tunnels over an HTTP/2 connection. If you want SSH remote forwarding, that is a different tool, and the comparison here is that go-http-tunnel multiplexes over HTTP/2 and identifies clients by TLS certificate ID.

## Sources

- [Issues](https://github.com/mmatczuk/go-http-tunnel/issues)
- [License: AGPL-3.0](https://github.com/mmatczuk/go-http-tunnel/blob/master/LICENSE)
- [mmatczuk/go-http-tunnel on GitHub](https://github.com/mmatczuk/go-http-tunnel)
- [README](https://github.com/mmatczuk/go-http-tunnel/blob/master/README.md)
- [Releases](https://github.com/mmatczuk/go-http-tunnel/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/mmatczuk-go-http-tunnel
