Go from Gelb to Grün
A freshly-onboarded tunnel is Gelb: live, trusted by browsers, but terminating TLS with a certificate
shared across every Gelb-tier tunnel on the platform. Grün means your service holds its own,
individually-issued certificate instead. See
Certificate tiers explained for why this
distinction exists at all, and
The three DNS-01 backends behind every cert on this platform
for how ct-agent certificate below actually proves you own the hostname, without ever holding a
zone-wide DNS credential.
Before you start
Your origin must serve plain HTTP, not TLS, while you do this — the ACME validation happens over the same connection your tunnel already forwards, and the platform (not your origin) is the one that will hold the TLS termination once Grün is issued. If your origin already speaks TLS, switch it to plain HTTP first and switch it back afterward.
Run it
CT_AGENT_CP_URL=https://bunsenbrenner.org \
CT_AGENT_TOKEN=<your tunnel's routing token> \
CT_AGENT_HOSTNAME=<your hostname> \
CT_ACME_CERT_OUT_DIR=./ct-agent-cert \
./ct-agent certificate
Real values from a run against production, for scale: this completed with a real, browser-trusted ZeroSSL certificate, and wrote:
./ct-agent-cert/
acme-account-key.der
fullchain.pem
privkey.pem
ct-agent certificate does not exit after obtaining the certificate — it’s a persistent renewal
daemon (run_renewal_loop, checking every 6 hours whether renewal is due) that keeps running until you
stop it. If you only want the certificate and don’t want a long-lived process hanging around, Ctrl+C
(or send it SIGTERM) once you’ve confirmed Grün below — the files it already wrote are yours to keep;
you’d just be responsible for renewing manually before they expire. It does not replace your running
tunnel process either way; run it alongside, not instead of, the agent that’s already serving.
Confirm it worked
curl -s https://<your-cp-url>/agent/acme-admission/<your-routing-token>/<your-hostname>
should now report "status":"gruen". Once it does, point your origin’s own TLS termination at the
fullchain.pem/privkey.pem pair ct-agent just wrote, and switch it back from plain HTTP.
If it hangs or fails
The most common cause is DNS-01 propagation timing: the platform’s own DNS backend accepted your hostname’s validation record, but the certificate authority checked for it before that record had actually propagated to the public nameservers it queries. This is a timing issue, not a configuration error — retry the command; it doesn’t cost you anything to redo (unlike onboarding, this isn’t a single-use token).
The control plane itself already waits for convergence before responding to ct-agent’s challenge
call — up to five minutes in practice — so a run that takes noticeably longer than a normal
certificate issuance but eventually reports gruen is expected, not a bug. If instead the command
fails quickly (within ~10 seconds) with a connection/timeout error rather than actually hanging,
that’s a known agent-side client-timeout mismatch
(CADS-Tunnel#809, fixed control-plane-side; tracked
for the agent’s own release in ct-agent#217) — retrying
won’t help in that specific case, since each attempt starts a fresh ACME order and can’t accumulate
propagation progress from the previous one. Check ct-agent --version against the fix’s release notes
once one ships.
If it’s not that — the command runs but never seems to get a CA to actually issue against — you may be waiting on the platform’s admission queue rather than anything on your end; check your tunnel’s row in the portal to see whether you’re queued, offered (with a 48h clock running), or lapsed.
Bringing your own certificate instead
ct-agent certificate isn’t the only path to Grün — strict or air-gapped setups can supply their own
certificate and key directly instead of running this platform’s ACME flow at all. Install your own cert
(from any CA) on your origin yourself, then tell the platform you’re ready:
curl -X POST https://<your-cp-url>/agent/acme-issuance-complete/<your-routing-token>/<your-hostname>
See API endpoints for what this actually does — the control plane doesn’t verify a certificate exists, it trusts the routing token and reverts the edge to passthrough, genuinely Grün either way.
403 no matter how correct your routing token is. See
[Authorize a new pipeline hostname](/how-to/authorize-a-pipeline-hostname/)
for the exact gotcha and the fix — found live, the hard way, this session.