ghostunnel
ghostunnel is a simple TLS proxy with mutual-authentication support, written in Go and maintained at ghostunnel/ghostunnel. Its job is to wrap plaintext applications in TLS — either terminating TLS in front of an insecure backend, or giving an insecure local client a TLS-speaking front end — without modifying the application itself.
It runs in two modes:
- Server mode — accepts TLS connections and proxies them to an insecure backend (TCP host/port or UNIX socket). Enforces mutual TLS by requiring valid client certificates, with fine-grained access control on certificate fields (
--allow-cn,--allow-ou,--allow-dns,--allow-uri) or declarative authorization via Open Policy Agent policies. - Client mode — accepts insecure local connections (TCP or UNIX socket) and proxies them to a TLS-secured upstream, presenting a client certificate. Target verification is hostname-based by default;
--override-server-nameoverrides the name used for verification when the server’s certificate CN/SAN doesn’t match the dial address (common when tunneling to an IP or through an SSH forward).
Key properties from the project’s docs:
- Certificate sources — PEM (
--cert/--keyor combined), PKCS#12 (--keystore), ACME (Let’s Encrypt, server mode), PKCS#11 HSMs (including YubiKey), macOS/Windows keychains, and the SPIFFE Workload API. - Certificate hotswapping — reload cert/key/CA on
SIGHUP/SIGUSR1or a--timed-reloadinterval, enabling short-lived certificates without restarts.SO_REUSEPORTallows a replacement instance to bind before the old one exits. - Secure by default — listeners and targets are restricted to localhost/UNIX sockets unless
--unsafe-listen/--unsafe-targetis passed; on Linux, Landlock sandboxing is enabled by default. - Metrics — optional status port with JSON and Prometheus endpoints, plus pprof profiling.
Usage pattern
A typical client-mode deployment that fronts a plaintext app with mTLS:
# Server side: terminate TLS in front of a plaintext backend on :8080.
ghostunnel server \
--listen localhost:8443 \
--target localhost:8080 \
--keystore server-keystore.p12 \
--cacert cacert.pem \
--allow-cn client
# Client side: app talks plaintext to localhost:8000; ghostunnel
# wraps it in mutually-authenticated TLS to the upstream.
ghostunnel client \
--listen localhost:8000 \
--target upstream.example.com:8443 \
--keystore client-keystore.p12 \
--cacert cacert.pemPoint the application at http://localhost:8000 and ghostunnel handles the handshake, client-certificate presentation, and encryption. When the upstream is reached through a jump host, a plain ssh -L forward can bring the target to a local port first, with ghostunnel layering mTLS on top.
Testing with openssl s_client -cert ... -key ... -CAfile ... is the standard way to verify the mTLS handshake from the outside; see openssl-s-client-command-injection and pull-ssl-certificates-external-server for related OpenSSL client workflows.
Assessment relevance
On engagements, ghostunnel (or its cousin stunnel) shows up as the TLS shim in front of internal services — databases, message queues, admin panels. Finding the client keystore plus its CA bundle on a compromised host means you can stand up your own client-mode ghostunnel and reach the mTLS-protected backend as a legitimate peer, bypassing the perimeter entirely. The --allow-* flags on the server side also tell you exactly which certificate fields the target trusts (e.g. only CN), which shapes what a forged or mis-issued certificate needs to contain.
Sources
- ghostunnel/ghostunnel — A simple TLS proxy with mutual authentication
- Ghostunnel — A simple TLS proxy with mutual authentication support
See also
- socat — general-purpose relay that can also wrap connections in TLS
- chisel — tunneling in the opposite direction (HTTP/SSH-style pivoting)
- openssl-s-client-command-injection