# ghostunnel: a small TLS proxy that puts mutual auth in front of a service

> Written in Go, shipped as one binary, and aimed at exactly one job: terminating TLS with client certificates so that an application which knows nothing about crypto can sit behind it.

**ghostunnel/ghostunnel** — A simple TLS proxy with mutual authentication for securing non-TLS services.

- Repository: https://github.com/ghostunnel/ghostunnel
- Website: https://ghostunnel.dev/
- Stars: 2,200 · Forks: 288
- Language: Go
- License: Apache-2.0
- Published: 2026-10-06 · Updated: 2026-10-06 · Language: en
- Canonical page: https://hysenlabs.com/projects/ghostunnel-ghostunnel

## Two modes that do the same job from opposite directions

The README describes the whole tool in one paragraph, and it is worth reading closely because the two modes are easy to confuse. In server mode, Ghostunnel runs in front of a backend, accepts TLS-secured connections, and proxies them to the insecure backend, which can be a TCP host and port or a UNIX domain socket. In client mode, the direction reverses: Ghostunnel accepts insecure connections on a TCP or UNIX socket and proxies them to a TLS-secured service.

Server mode is the common case. You have an application that listens on `localhost:8080` and speaks nothing but clear TCP, and you need callers to prove who they are before the application sees a byte. Ghostunnel listens on `localhost:8443`, terminates TLS, and forwards.

Client mode exists for the opposite problem: an insecure local service needs to reach a remote endpoint that insists on mutual TLS, and you would rather not change the local service's code. Ghostunnel takes the local connection, adds TLS on the way out, and presents a client certificate.

Both modes speak the same underlying idea, byte streams in and byte streams out. There is no HTTP parsing, no header rewriting, and no protocol awareness. That narrowness is the design, and it is why the tool stays small enough to reason about.

Platform support is broader than the release matrix suggests. The README says Ghostunnel is developed and tested on Linux, macOS and Windows, and also runs on most other UNIX systems supported by Go such as FreeBSD, OpenBSD and NetBSD.

## Building it and generating throwaway certificates

Ghostunnel uses the mage build system, a make and rake style tool written in Go, available as a Go tool dependency with no separate install step. The README is explicit that the official release binaries are best effort, usually built directly via GitHub Actions on the latest available images, and that building yourself is the recommendation if you need compatibility with specific OS versions.

```bash
go tool mage go:build
go tool mage docker:build
```

Running `go tool mage -l` lists every build target, and `-v` makes the commands more verbose. Container images are published to ghostunnel on Docker Hub, and the repository tree carries four Dockerfiles: `Dockerfile-alpine`, `Dockerfile-debian`, `Dockerfile-distroless` and `Dockerfile-test`, which tells you the distroless variant is a supported deployment target rather than an afterthought.

Before you can start the proxy you need certificates, and the README makes a useful point about getting throwaway ones. If you already run a PKI, use it. Otherwise it points at mkcert and cloudflare/cfssl. For quick testing there is a built-in generator:

```bash
go tool mage test:keys
```

That writes a `test-keys` directory with the certificates and keys needed for testing, and the README marks them clearly as test certificates that must not be used in production. The tests themselves need Python 3 and run either locally or in a container:

```bash
go tool mage test:all
go tool mage test:docker
```

The Docker variant also runs the PKCS#11 integration tests against SoftHSM inside the container, which is the detail that tells you hardware security module support is tested rather than assumed.

## Client certificate checks that are more than a boolean

Mutual TLS is the point of the tool, and the access control on top of it is where it gets interesting. To set allowed clients you must specify at least one of `--allow-all`, `--allow-cn`, `--allow-ou`, `--allow-dns`, `--allow-uri`, or `--allow-policy`. Every check runs against the certificate the client presented.

The mapping is straightforward. `--allow-cn` and `--allow-ou` match the Common Name and Organizational Unit fields, `--allow-dns` and `--allow-uri` match subject alternative names. The distinction between DNS and URI SANs matters in practice, since a client certificate issued for a hostname and one issued for a SPIFFE ID do not put the same value in the same place.

Multiple flags are a logical disjunction, meaning a client connects as long as any of the flags matches. That is a permissive default for policy composition and it is worth being deliberate about, because the natural expectation from a list of constraints is that they narrow. They widen. If you want intersection behaviour you need one of the declarative routes rather than several flags.

Two more mechanisms sit alongside the field checks. Declarative authorization policies run through Open Policy Agent, wired in as the `policy/` directory in the tree with the OPA Go library in the module manifest. And `--allow-spki-pin` authenticates clients by out-of-band SPKI key pinning, meaning the key hash is the credential rather than a chain to a CA. The 1.11.1 release notes are explicit that with pinning the peer's certificate chain, validity period and hostname are not verified, which is exactly the trade you accept when you skip the PKI.

If you have an Open Policy Agent setup already, `--allow-policy` is the feature that makes Ghostunnel fit an existing governance model rather than adding a parallel one.

## Where certificates come from, and reloading them without downtime

The `--keystore` flag accepts a PKCS#12 keystore or a combined PEM file holding the chain and private key, with the format auto-detected. JCEKS and JKS keystores are supported for legacy use cases. When you would rather keep the chain and key apart, `--cert` and `--key` load them from separate PEM files.

That covers the ordinary filesystem cases, and then the list gets more interesting. Ghostunnel can load an identity from ACME, meaning Let's Encrypt. It can hold private keys in a PKCS#11 hardware security module, backed by the letsencrypt/pkcs11key library. It can read from the macOS keychain and the Windows Certificate Store. And it can fetch an identity from the SPIFFE Workload API, which is what lets a workload get a short-lived certificate without any secret in the filesystem or environment.

The `certloader/` and `certstore/` directories in the tree correspond to those two halves of the problem: getting bytes out of a source, and keeping them in memory in a form the TLS stack can use.

Reloading matters as soon as certificates are short-lived, which is the direction the whole industry is moving. Ghostunnel reloads on SIGHUP or SIGUSR1, or on a timed reload interval, without a restart. Restarting a proxy to pick up a new certificate is the kind of operation that becomes the reason someone did not move to short-lived certificates in the first place, so having it as a non-event is a real feature rather than a nicety.

The 1.11.1 notes show this area getting attention: a batch of reliability fixes across certificate reload covering PKCS#11 and the macOS and Windows keychains.

## Safe defaults, Landlock, and the metrics port

A tool that terminates TLS and forwards traffic has an obvious failure mode, which is being configured to listen on a public interface by accident. Ghostunnel addresses that directly. Listeners and targets are restricted to localhost and UNIX sockets unless you explicitly override with `--unsafe-listen` or `--unsafe-target`. The flag names do the documenting.

On Linux there is a second layer. Landlock sandboxing is enabled by default to limit process privileges, and the tree has `landlock_linux.go` beside `landlock_other.go`, which is the standard shape for a capability that exists on one kernel and degrades elsewhere. The `github.com/landlock-lsm/go-landlock` dependency is in the module manifest, and `security/` documentation covers the general model.

Operating it is straightforward. Ghostunnel runs in the foreground and logs to stdout by default; `--syslog` switches to syslog instead. If you want it in the background the README recommends a service manager, and the tree has `signals.go`, `windows_service.go` and socket activation support for systemd and launchd, so the process model is designed to be supervised rather than daemonised by itself.

Observability comes from a built-in status port offering JSON and Prometheus metrics endpoints, with optional pprof profiling. `status.go` and its platform-specific siblings are where that lives. The 1.11.1 notes also mention that metric collection is skipped entirely when no metrics sink is configured, which is the kind of hot-path detail that shows up in a proxy's latency budget.

Beyond the core job the README lists UNIX domain sockets, PROXY protocol v2, systemd and launchd socket activation, and Windows service management. The PROXY protocol support matters if something downstream needs the original client address rather than Ghostunnel's.

## Release cadence, dependency weight, and the honest limits

Version 1.11.3 was published on 2026-08-22 and the last push was on 2026-09-28, so this is active work rather than an abandoned tool. With 4 open issues, the tracker is unusually quiet, which is either a sign of a small and well-understood problem or a sign that people file issues elsewhere. The 1.11.3 release notes are detailed to the point of documenting edge cases: the new `--alpn` flag sets comma-separated protocols in preference order, works in both modes, is disabled by default, rejects a client offering ALPN with no protocol in common using a `no_application_protocol` alert, still connects clients that do not use ALPN at all, rejects malformed lists at startup, and is deliberately ignored by the status port so monitoring clients cannot be locked out.

That level of care extends to the regressions. Version 1.11.2 restored UNIX socket targets in client mode, which the stricter validation added in 1.11.0 had broken, and it notes the consequence honestly: a `unix:PATH` target carries no hostname, so you need `--override-server-name` to give hostname verification something to check.

Where to be careful is scale and shape. This is a byte-stream proxy with one process and no clustering, no configuration reload from a remote source, and no protocol awareness. It is the right shape for protecting a handful of internal services, and the wrong shape for high-throughput traffic or for anything where you need connection-level load balancing.

The dependency list is worth a glance too. `go.mod` pulls in OPA, SPIFFE, kingpin for flags, certmagic and acmez for ACME, Prometheus client metrics, landlock, and several PKCS#11 and PKCS#12 libraries. That is a lot more than the word simple suggests, though the argument count for a Go binary is a weak objection in practice. Licensing is Apache-2.0, recorded in `LICENSE` at the root and stated in the repository description.

## Conclusion

ghostunnel is the right tool for a specific and fairly common shape of problem: a service that listens on a local port, speaks plain TCP, and needs to be reachable only by clients holding a certificate your organisation already issues. One static Go binary, mutual TLS by default, access decisions on certificate fields, and no application change. What it will not do is replace an application that speaks HTTP with its own auth model, or handle anything richer than byte streams. Two defaults are worth knowing before you deploy it: listeners and targets are restricted to localhost and UNIX sockets unless you pass `--unsafe-listen` or `--unsafe-target`, and on Linux Landlock sandboxing is on unless you turn it off. Start with `ghostunnel server --help`, pick an `--allow-*` policy, and read the access flags document before choosing between certificate field checks and OPA.

## FAQ

### What is Ghostunnel used for?

It secures a backend service that speaks plain TCP by terminating TLS in front of it and requiring client certificates. In server mode it accepts TLS connections and proxies them to an insecure backend, in client mode it accepts insecure local connections and proxies them to a TLS service. The backend can be a TCP host and port or a UNIX domain socket.

### How does Ghostunnel decide whether a client is allowed to connect?

You must set at least one of `--allow-all`, `--allow-cn`, `--allow-ou`, `--allow-dns`, `--allow-uri` or `--allow-policy`, and each check runs against the client certificate. Multiple flags are treated as a logical disjunction, so a client connects if any flag matches. Declarative policies go through Open Policy Agent, and `--allow-spki-pin` authenticates by key hash with no CA involved.

### Can Ghostunnel load certificates from a hardware security module or the macOS keychain?

Yes. The README lists PEM and PKCS#12 files, JCEKS and JKS for legacy use, ACME for Let's Encrypt, PKCS#11 hardware modules, the macOS keychain, the Windows Certificate Store and the SPIFFE Workload API. Certificates can be reloaded on SIGHUP or SIGUSR1 or on a timed interval, so short-lived certificates do not require a restart.

### How do I build Ghostunnel from source?

It uses the mage build system, available as a Go tool dependency with no separate install. Run `go tool mage go:build` for a binary and `go tool mage docker:build` for containers; `go tool mage -l` lists all targets. The README notes the official release binaries are best effort, and recommends building yourself when you need compatibility with specific OS versions.

## Sources

- [ghostunnel/ghostunnel on GitHub](https://github.com/ghostunnel/ghostunnel)
- [License: Apache-2.0](https://github.com/ghostunnel/ghostunnel/blob/master/LICENSE)
- [Project website](https://ghostunnel.dev/)
- [README](https://github.com/ghostunnel/ghostunnel/blob/master/README.md)
- [Releases](https://github.com/ghostunnel/ghostunnel/releases)

---

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