# ngrok-go: a public endpoint for a Go server in one net.Listener

> ngrok-go is the ngrok agent packaged as an idiomatic Go library, so a program that already speaks net.Listener can be reachable from the internet without touching certificates, load balancers or port forwarding. The tradeoff is that every connection now depends on ngrok's cloud service and on a tunnel, and the traffic policy engine can also act on your requests before they reach your code.

**ngrok/ngrok-go** — Embed ngrok secure ingress into your Go apps as a net.Listener with a single line of code.

- Repository: https://github.com/ngrok/ngrok-go
- Stars: 880 · Forks: 106
- Language: Go
- License: MIT
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/ngrok-ngrok-go

## The agent as a library, and the interface it hands back

The README explains the concept in a single comparison. If you have used ngrok before, you can think of ngrok-go as the ngrok agent packaged as a Go library.

That framing matters because it tells you where the complexity went. The ngrok agent is a separate process that maintains a tunnel to the cloud service, and the library moves that into your program so there is nothing to start, no configuration file to write and no second process to supervise.

What comes back is an ordinary Go listener. The documentation says applications using ngrok-go listen on ngrok's global cloud service but receive connections using the same interface, net.Listener, that any Go app would expect if it listened on a local port. That is the whole trick, and it is why the project description calls it a single line of code.

The mechanism in the example is that ngrok.Listen uses ngrok.DefaultAgent, and the agent uses the NGROK_AUTHTOKEN environment variable for auth. The returned value is a net.Listener, and it is handed straight to http.Serve, so nothing about the HTTP stack changes. Your handler is unaware that the traffic arrived from the internet through a data centre rather than from localhost.

The repository layout is what a mature Go library looks like. There is a listener.go, an agent.go with agent_options.go, a forwarder.go, a session.go, an endpoint.go with endpoint_options.go, a defaults.go, an events.go, a metrics.go, an upstream.go, an httpinspect.go and a diagnose.go, each with its test file alongside. There is an rpc directory, an internal directory, and an examples directory with fasthttp, forward, http, multiple-endpoints, rpc, tcp and traffic-policy cases.

The go.mod is short, and the one dependency that stands out is golang.ngrok.com/muxado/v2, the transport the agent uses, alongside jpillora/backoff for retry behaviour, multierr for error handling and protobuf for the wire format.

## The quickstart is an authtoken and a run command

Installing is a single go get, and the module path is a vanity import that resolves to GitHub:

```sh
go get golang.ngrok.com/ngrok/v2
```

The quickstart example is a complete Go program, and it is worth reading in full because it shows the whole shape of the integration.

```go
ln, err := ngrok.Listen(ctx)
if err != nil {
	return err
}

log.Println("Endpoint online", ln.URL())

// Serve HTTP traffic on the ngrok endpoint
return http.Serve(ln, http.HandlerFunc(handler))
```

The URL method on the listener gives you the public address, which is what you then hand to a webhook sender or a colleague. The example runs with:

```sh
NGROK_AUTHTOKEN=xxxx_xxxx go run examples/http/main.go
```

and the authtoken comes from the ngrok dashboard. The documentation is careful to say that the URL provided when running the example is accessible by anyone with an internet connection, which is a warning worth taking literally. A tunnel to a development machine is a public endpoint with whatever authentication your handler happens to have, which is often none.

The context passed to ngrok.Listen matters too. It is the run function's context, and since the listener is a long-lived resource, cancelling the context is how a program shuts the tunnel down. The example passes context.Background(), so a real program would want a cancellable context tied to the server's lifetime.

The module also ships a console entry point pattern, since the example lives in examples/http/main.go and the repository has its own go.mod in the examples directory, which is the standard way to keep example dependencies from polluting the library's own module.

## The traffic policy engine acts before your handler does

The second half of the feature set is the one with a security dimension, and it is the part an evaluator should read first.

ngrok's Traffic Policy engine applies API Gateway behaviours at ngrok's cloud service, so the actions happen before the request reaches your process. The documentation lists four categories: auth, route, block and rate limit.

The example policy is a YAML document, and it shows all of them working together. A rate limit by IP address is defined with a sliding window algorithm, a capacity of 30 and a rate of 60s, keyed on conn.client_ip. Then a federated Google OAuth action. Then a block on users whose OAuth identity email does not end with @example.com, expressed as a negated endsWith check, with a custom-response action returning status code 403 and the identity name interpolated into the content.

The policy is passed to ngrok.Listen as an option:

```go
ln, err := ngrok.Listen(ctx, ngrok.WithTrafficPolicy(tp))
```

The important thing to understand is where this runs. The policy is evaluated at ngrok's cloud service, not in your program, so a 403 for a user on the wrong domain happens without a packet reaching your handler. That is genuinely useful, since it means the rejection does not consume your application's resources and does not depend on your code being correct.

It also means the behaviour of your endpoint is partly defined by a document you send to a third party, in a syntax that is not Go and is not your code. A misconfigured policy expression is a security bug that no test in your repository will catch, and the example's block condition is itself a good illustration of how easy it is to write a condition that looks right and is not.

The traffic policy example directory in the repository is where the full set of cases lives.

## Four ways to use the tunnel that are not the same thing

The examples list names four different patterns, and they differ in a way that matters for how you architect around the library.

Creating a TCP endpoint and handling TCP connections directly means your program speaks the raw protocol over the tunnel, which is how you would expose a database, a Postgres wire protocol client or a custom binary service. Forwarding to another URL means the library does not handle connections itself, it relays to a target, which is how you would put a tunnel in front of something that already runs. Adding Traffic Policy in front of your app is the pattern from the previous section. And the examples directory also includes a multiple-endpoints case and an rpc case, with a fasthttp case for the alternative Go HTTP server.

That last one is worth noting. The Go standard library's net/http is not the only server, and fasthttp is a common choice for high throughput services, so the library shipping a fasthttp example tells you the net.Listener interface is being respected rather than worked around.

The multiple-endpoints example is the one that most affects architecture, because a net.Listener is a single stream of connections and a program that wants several public endpoints has to create several listeners. The repository has a multiple-endpoints example directory and the API surface includes endpoint.go with endpoint_options.go, which suggests endpoints are created as first class objects rather than by opening several listeners by hand.

The forwarder.go and upstream.go files in the root are the other half of the picture, since they cover the case where the library is relaying rather than serving.

## Where an embedded tunnel is the wrong architecture

Three situations rule it out, and the first one is about what you are depending on.

The first is a production service that has to stay up. With ngrok-go, the availability of your endpoint is the availability of the ngrok cloud service plus the availability of your process plus the availability of the tunnel between them. A Go server on a public cloud load balancer has one dependency, its own infrastructure, and you can reason about its failure modes. A tunnel has three, and the documentation does not describe what happens to in-flight requests when the tunnel drops, or how a client is told to retry.

The second is inbound traffic at scale. The model here is a client process holding an agent connection to the cloud service, and the documentation does not describe a high volume deployment. If you need many connections or many endpoints, the design to reach for is a public ingress with a stable address and a client that dials out to a control plane, which is what the agent architecture is, but you would want to read how the tunnel is load balanced before betting on it.

The third is a case where the traffic policy becomes a liability. The policy engine is a genuine feature, but it also means part of your access control lives in a document evaluated by a third party. If your security model requires that the authorisation decision is made by code you own and can test, then putting an OAuth federation and a rate limit in the policy layer is one step too far.

The honest alternative for most of these cases is the one ngrok itself pushes, an ngrok API client library for working with the API rather than embedding the agent, and the project's own documentation points at it for working with the ngrok API. If what you want is to create and manage endpoints programmatically, including from a service that runs somewhere else, that is the right library, and this one is for the case where the program and the tunnel should live together.

## Version policy, licence, and the shape of the maintenance

Two details decide how much of a risk adopting this library is.

The first is the Go version policy, and it is short. The project supports the current and previous major releases of Go. The go.mod requires Go 1.25.7, and the module path carries a v2 suffix, so the major version is in the import path as the Go convention requires, which means an existing v1 import in your code has to change when you upgrade.

The release record shows a project shipping steadily. The recent versions are v2.2.1 on 2026-10-16, v2.2.0 on 2026-08-11 and v2.1.4 on 2026-04-27, and the last push to the repository was on 2026-10-19 with the repository not archived.

The licence is MIT, and the README states it in full, with the terms in a LICENSE.txt file. There is also a CODE_OF_CONDUCT.md, a CONTRIBUTING.md pointing at the contributing guide, a CHANGELOG.md for tracking changes, and a TODO.md. The repository carries a .golangci.yml, so the code is linted, and there is a flake.nix with a flake.lock, which means the development environment is pinned with Nix.

The build and test workflow is a GitHub Actions badge on the buildandtest workflow, and the Go reference badge points at pkg.go.dev for golang.ngrok.com/ngrok/v2, so the package documentation is generated rather than hand written.

Two things are worth flagging that the README does not. The first is that the documentation warns the examples in the launch announcement blog post may be out of date for the new API, which is a hint that the v2 API is still settling. The second is that the library depends on an external service for its core function, so the practical upgrade question is not only whether the Go API changed but whether the tunnel protocol and the cloud side did, and the CHANGELOG.md is the place to look for that.

## Conclusion

ngrok-go fits a Go developer who needs a public URL for a service on their own machine, a webhook receiver during development, or a demo where the client cannot reach a private network. It is the wrong choice for a production deployment that must survive the tunnel vendor having a bad day, and it is the wrong choice if you need inbound connections at scale, since the documented model is one client process holding an agent connection. Before adopting it, read the traffic policy section in the project documentation, because the policy engine can terminate authentication and rate limiting before your handler runs, and confirm that your authtoken handling and the default agent behaviour match how you intend to expose the endpoint.

## FAQ

### What is ngrok-go and what does it replace?

It is an open source Go library that embeds ngrok networking directly into a Go application, so the program listens on ngrok's global cloud service and receives connections through the same net.Listener interface it would use for a local port. It removes the need to set up IPs, certificates, load balancers and ports by hand, and it is the ngrok agent packaged as a library.

### How do I install and use ngrok-go?

Run go get golang.ngrok.com/ngrok/v2, then call ngrok.Listen with a context and pass the returned listener to http.Serve. The example in the repository is run with NGROK_AUTHTOKEN=xxxx_xxxx go run examples/http/main.go, and the authtoken comes from the ngrok dashboard.

### What can ngrok's Traffic Policy do in ngrok-go?

It applies API Gateway behaviours at ngrok's cloud service before a request reaches your application, covering authentication, routing, blocking and rate limiting. The policy is a YAML document passed with ngrok.WithTrafficPolicy, and the documented example combines an IP rate limit, Google OAuth federation and a 403 for identities outside a domain.

### What usage patterns does ngrok-go support?

The examples cover creating a TCP endpoint and handling connections directly, forwarding to another URL instead of handling connections, adding a traffic policy in front of the app, and running multiple endpoints, with separate directories for fasthttp, forward, http, multiple-endpoints, rpc, tcp and traffic-policy.

### What licence and Go version support does ngrok-go have?

It is licensed under the MIT licence with the terms in LICENSE.txt. The project supports the current and previous major releases of Go, the module requires Go 1.25.7, and the import path carries a v2 suffix. The recent releases are v2.2.1 on 2026-10-16, v2.2.0 on 2026-08-11 and v2.1.4 on 2026-04-27.

## Sources

- [Issues](https://github.com/ngrok/ngrok-go/issues)
- [License: MIT](https://github.com/ngrok/ngrok-go/blob/main/LICENSE)
- [ngrok/ngrok-go on GitHub](https://github.com/ngrok/ngrok-go)
- [README](https://github.com/ngrok/ngrok-go/blob/main/README.md)
- [Releases](https://github.com/ngrok/ngrok-go/releases)

---

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