Real HTTPS for any IP,
with a proxy you run.
goTLS gives you magic wildcard DNS and a keyless signer; you run the helper proxy. TLS is terminated on your own machine. The only thing that ever reaches goTLS is the handshake signature, a few hundred bytes over UDP. Your traffic never leaves your control.
Point any of these at your own machine, run the proxy, and open it over HTTPS. The certificate is valid, no key needed. IPv4 maps to an A record, IPv6 to an AAAA record.
Sponsored by trion development GmbH — built with ♥ for developers, homelabs & CI.
How the naming works
Embed an IP address in the hostname: an IPv4 as dashed or dotted octets, an IPv6 with every colon written as a dash (so :: becomes --), or either as a single hex label. Each row below resolves to the address on the right.
| Hostname | Style | Resolves to |
|---|---|---|
| 10-0-0-1.gotls.de | dashed · A | 10.0.0.1 |
| app.10-0-0-1.gotls.de | dashed + label · A | 10.0.0.1 |
| app-10-0-0-1.gotls.de | dash-joined · A | 10.0.0.1 |
| 10.0.0.1.gotls.de | dotted · A | 10.0.0.1 |
| app.10.0.0.1.gotls.de | dotted + label · A | 10.0.0.1 |
| 0a000001.gotls.de | hex · A | 10.0.0.1 |
| 2001-db8--1.gotls.de | colons→dashes · AAAA | 2001:db8::1 |
Why keyless: Your traffic stays yours
Hosted TLS proxies see all your traffic;. Ordinary wildcard-TLS services publish the private key so anyone can decrypt. goTLS does neither. You run the proxy, so TLS is terminated on your own machine and your bytes flow client -> your proxy -> your service without ever leaving your network. The only thing that crosses to goTLS is the keyless handshake signature. A few hundred bytes (over UDP). No payload, not one byte of your traffic, reaches goTLS or any third party.
few hundred bytes via UDP
Client, proxy, and service are all on your infrastructure; only the thin signature hop to goTLS's signer ever leaves it. The proxy is a small Rust binary; goTLS's signer and DNS is a Java service.
Not just HTTPS
The proxy terminates TLS by SNI and then forwards raw bytes. This is TCP. So any protocol that rides on TLS gets a valid certificate the same way, while the private key stays secured on the signer.
IoT & MQTT
Point a whole fleet at broker.10-0-0-5.gotls.de and every device gets a trusted MQTTS connection. The broker's private key never leaves the signer.
Mail & messaging
SMTPS and IMAPS connect without warnings too. Implicit-TLS mail servers get the same trusted certificate as a web host.
gRPC & any TLS service
gRPC, databases, message queues. If it speaks TLS, it works. The edge terminates the handshake; the payload is just bytes on the way to your backend.
Get started
Two steps: You start the proxy first, and then the magic URL works.
Start your proxy
Run the keyless proxy (container image or a static binary) on a network where your service lives.
A single catch-all route sends every *.gotls.de host to your service. It terminates TLS locally and only asks goTLS's signer for the handshake signature, so your traffic never leaves your machine.
# One catch-all edge: every *.gotls.de host -> your local service. # The only value that is yours is ROUTE_0_UPSTREAM - remainder are goTLS constants. $ docker run --rm --network host \ -e PROXY_LISTEN=0.0.0.0:443 \ -e ROUTE_0_UPSTREAM=127.0.0.1:8080 \ -e ROUTE_0_SNI='*.gotls.de' \ -e ROUTE_0_SIGNER=gotls.de:9000 \ ghcr.io/trion-development/gotls-proxy:1.0.0
# Same as the container but as a static binary (no runtime, no container). # :443 is privileged - let the unprivileged binary bind it once (or use sudo). $ sudo setcap 'cap_net_bind_service=+ep' ./keyless-proxy $ PROXY_LISTEN=0.0.0.0:443 \ ROUTE_0_SNI='*.gotls.de' \ ROUTE_0_UPSTREAM=127.0.0.1:8080 \ ROUTE_0_SIGNER=gotls.de:9000 \ ./keyless-proxy
On startup the proxy prints its listening interface IP and port as well as a dashed URL to be used (e.g. https://10-0-0-5.gotls.de).
Step 2 is just copy-and-paste.
Use --help for the full route syntax.
Prefer a prebuilt binary? Grab one from the download page.
Open your service over HTTPS
Use the magic hostname for the IP your proxy is listening on. The certificate is trusted, so browsers, curl, and webhooks all connect without warnings.
# With PROXY_LISTEN=0.0.0.0:443 the URL needs no port at all. curl https://127-0-0-1.gotls.de/ # A box on your LAN running the proxy, valid certificate and all curl https://192-168-1-20.gotls.de/ # A public dev server. Share the URL with teammates open https://app-203-0-113-9.gotls.de/
This works only while your Step-1 proxy is running and reachable at that IP: goTLS just answers DNS and signs the handshake
Your proxy does the TLS and serves your app. Ideal for local development, demos, IoT, homelabs, and CI.
FAQ
The short version: goTLS answers DNS and signs TLS handshakes. You run the proxy that terminates TLS. Your traffic stays yours.
Is it free?
Yes. goTLS is free.. There is no signup and no cost.
Why the name "goTLS"?
It's short and quick to type, and it carries the idea: go TLS is a nudge to move everything to TLS, even a throwaway dev box or a small device.
How does this relate to nip.io and traefik.me?
xip.io, nip.io and traefik.me pioneered magic wildcard DNS.
goTLS borrows the same naming trick.
The TLS half is inspired by Cloudflare's Keyless SSL:
Get valid HTTPS without ever handing over the private key. goTLS combines the two.
Why keyless? What problem does it solve?
To give everyone valid HTTPS on a shared wildcard domain, a service like traefik.me has to publish its wildcard private key so your local proxy can complete the TLS handshake.
That is exactly what killed it:
In this discussion, it is explained that Let's Encrypt blocked the key and revoked its certificates because publishing a private key violates the CA/Browser Forum Baseline Requirements (§9.6.3 - a subscriber must keep the private key confidential at all times).
Keyless SSL sidesteps the whole problem: the private key never leaves goTLS's signer, so there is nothing to publish and nothing to revoke.
Your proxy asks the signer to sign each handshake instead.
Can I combine this with my reverse proxy?
Yes, that is a sensible setup.
Put the keyless proxy at the front on :443 and let it hand traffic to Traefik, nginx, Caddy, HAProxy.
Whatever you already run.
The proxy leverages SNI: Terminate the goTLS handshake and forward plaintext TCP/HTTP to your reverse proxy, or pass the raw TLS stream through untouched.
So your reverse proxy terminates it with its own certificate.
The following example shows how to use it in front of Traefik.
*.gotls.de is keyless-terminated and handed over as HTTP, while your own domains are passed straight through to Traefik's HTTPS entrypoint:
# docker-compose.yml example for proxy in front of Traefik services: gotls: image: ghcr.io/trion-development/gotls-proxy:1.0.0 ports: ["443:443"] environment: PROXY_LISTEN: 0.0.0.0:443 # 1) terminate *.gotls.de (keyless) -> plaintext to Traefik ROUTE_0_SNI: "*.gotls.de" ROUTE_0_UPSTREAM: traefik:80 ROUTE_0_SIGNER: gotls.de:9000 # 2) pass your own domains through -> Traefik does its own TLS ROUTE_1_SNI: "*.example.com" ROUTE_1_MODE: passthrough ROUTE_1_UPSTREAM: traefik:443 traefik: image: traefik:v3 command: - --entrypoints.web.address=:80 - --entrypoints.websecure.address=:443 - --providers.docker=true volumes: - /var/run/docker.sock:/var/run/docker.sock:ro
ROUTE_1_MODE: passthrough is the whole trick:
For those names goTLS never sees the plaintext or a key.
It just splices the encrypted stream to Traefik, which terminates it.
Prefer a single fallback?
Drop the passthrough route and set PROXY_DEFAULT_UPSTREAM: traefik:443 to pass everything unmatched straight through.
Either way *.gotls.de gets keyless TLS and everything else keeps your existing certificates.
Can I change the listening port or listen on multiple ports?
The port, yes: set PROXY_LISTEN to any address and port (default 0.0.0.0:8443; use 0.0.0.0:443 for standard HTTPS so the URL needs no port, or [::]:443 for IPv6). The examples above already do this.
Ports below 1024 are privileged: the container image runs as root, so it binds :443 out of the box; for the raw binary, grant the capability once with sudo setcap 'cap_net_bind_service=+ep' ./keyless-proxy (or run it with sudo).
Several ports at once from one process is not supported - PROXY_LISTEN is a single address. Just launch multiple proxy instances, one per port or interface. The binary is tiny, static and stateless, so running one on :443 and another on :8443 (each with its own routes) costs almost nothing: start one container per port, or one binary process each.
Does it work on Docker Desktop (Mac / Windows) or WSL2?
The image is multi-arch (amd64 and arm64), so it runs on Intel and Apple Silicon Macs, on Windows, and on Linux. What changes across platforms is the run command.
On native Linux, and on Docker installed inside a WSL2 distro, the command above works as written (--network host shares the real host network).
On Docker Desktop (Mac or Windows) containers run inside a small Linux VM, so --network host attaches to that VM rather than your machine, and 127.0.0.1 points at the VM, not at your app. Publish the port and use the host gateway instead:
docker run --rm -p 443:443 \ -e PROXY_LISTEN=0.0.0.0:443 \ -e ROUTE_0_UPSTREAM=host.docker.internal:8080 \ -e ROUTE_0_SNI='*.gotls.de' \ -e ROUTE_0_SIGNER=gotls.de:9000 \ ghcr.io/trion-development/gotls-proxy:1.0.0
-p 443:443 lets Docker Desktop forward your browser to the proxy, and host.docker.internal is the name Docker Desktop gives your host machine, so the proxy can reach your local app. This form also works on Linux and WSL2 if you would rather not use host networking.
I get a connection error
Most often the proxy is reachable but it cannot reach your upstream: The ROUTE_x_UPSTREAM host:port it forwards to. From the machine the proxy runs on, ensure the target host/port is actually reachable and listening. Verify with curl or nc:
curl -v http://127.0.0.1:8080/ # your ROUTE_0_UPSTREAM nc -vz 127.0.0.1 8080 # plain TCP reachability check
The proxy also logs the reason at warn level (visible at the default RUST_LOG=info), e.g. cannot reach upstream 127.0.0.1:8080: Connection refused, or an interrupted (reset/timeout) connection. So check its output. Then confirm the magic hostname resolves to the IP where your proxy actually listens, and that nothing else already holds the port.
Do you log or inspect my traffic?
goTLS can't! It never sees your traffic. You run the proxy that terminates TLS, so your bytes stay on your own machine. goTLS only answers DNS and signs handshakes; the signer receives a few hundred bytes per handshake and nothing else.
Where is the private key?
On goTLS's signer, never on your proxy. The proxy you run holds only the public certificate; every handshake signature is fetched from the goTLS signer over a compact UDP protocol. That's the "keyless" in the name.
How does the proxy obtain the certificate?
From the signer, over the same UDP protocol it uses for signing.
There is nothing to download or install by hand:
Point the proxy at the signer and it fetches the current public certificate chain when it starts, then re-checks it on an interval.
So when the certificate is renewed, the proxy picks up the new one on its own, with no redeploy.
Only the public certificate travels this way. The private key never does, it stays on the signer.
What about wildcard DNS?
goTLS is wildcard DNS:
Every *.gotls.de name resolves by pattern, with no records to create. The issue lies in the certificate (no phun intended).
A CA-issued wildcard like *.gotls.de is valid for exactly one label to the left of gotls.de, so the whole IP has to fit inside that single label.
Written with dots, 10.0.0.1.gotls.de is four labels, outside the wildcard, so it gets no valid certificate.
Written with dashes, 10-0-0-1.gotls.de is one label, so the same *.gotls.de certificate covers it (hex like 7f000001.gotls.de is a single label too).
That is the reason for dash notation: it keeps the whole address in a single DNS label that one wildcard cert can secure. (The dotted spelling still resolves for use just as DNS. It is the same method nip.io and traefik.me use.
The same reason IPv6 turns every : into - .)
IPv4 or IPv6?
Both. IPv4 resolves dashed (10-0-0-1) or dotted (10.0.0.1) to an A record; for HTTPS use the single-label dashed form, which the wildcard certificate covers (see "What about wildcard DNS?" above).
For IPv6, write every colon as a dash.
2001:db8::1 becomes 2001-db8--1.gotls.de and you get an AAAA record.
You can also encode the whole IP as one hex label: 7f000001.gotls.de is 127.0.0.1 (32 hex digits for IPv6), This is handy when a tool rejects dashes or dots.
Is it open source?
Not yet.
Why Rust, not Go?
The proxy sits on the TLS hot path and holds no keys, so it wants predictable low latency, memory safety with no GC pauses, and a tiny self-contained binary. Rust + rustls compiles to a single static musl executable (no runtime to ship), gives zero-cost async via Tokio, and lets us plug a custom remote signing key into the handshake, which is the whole point of keyless.
Go was a close call and is excellent for network services, but the garbage collector, the larger runtime footprint, and the need to hook TLS signing at a low level tipped it to Rust for a lean always-on edge that developers run themselves. The signer, which is not on the byte path, is Java.
Why Java for the signer?
The signer never sees your traffic: It answers a few hundred bytes per handshake. So raw throughput matters less than safe key handling, easy development and operational simplicity.
It's also a deliberate showcase that Java is cloud-native too: Compiled ahead-of-time with GraalVM native-image into a ~17 MB self-contained binary that starts in milliseconds and holds the signing key in about 15 MB of RAM (RSS).
No JVM. no warm-up.
The Container image is approximately 35 MB.
Zero runtime dependencies (libc only) keeping the native image small. For this to work the application has to be kept reflection-free. But we get Java's memory safety, PKCS#8/crypto libraries, and readable code at a footprint in the same league as the Rust proxy or a golang application would be.
Imprint
goTLS is a free service, provided and sponsored by trion development GmbH.
The service only resolves IP-embedding hostnames via DNS and signs TLS for them. It does not inspect, store, or log the traffic that passes through. It is offered as-is, with no warranty and no guarantee of availability; do not rely on it for anything you cannot afford to lose.