Diagnosing ngrok Custom Domain TLS Failures: Certificate Errors, Verification Issues, and Agent Misconfigurations
A diagnostic guide for ngrok custom domain TLS failures: recognize the 'certificate mismatch but tunnel online' condition, run ordered checks (domain verification, CNAME region, local service, firewall, authtoken), apply targeted fixes, and know when to escalate.
07 Mar 2026, 02:25 UTC

Recognizable Condition
You access your custom domain (for example, api.example.com) that tunnels through ngrok to a local service. The browser shows ERR_CERT_AUTHORITY_INVALID or "Your connection is not private," yet the ngrok dashboard reports the tunnel as online and the local service responds normally on localhost. This mismatch — healthy tunnel, broken TLS — is the starting point for diagnosis.
Cause and Diagnostic Table
| Symptom | Likely Cause | Diagnostic Check |
|---|---|---|
Custom domain serves ngrok's default *.ngrok.io certificate |
Domain not verified in ngrok dashboard | Open the Domains tab in the dashboard; the domain must show Verified with a green checkmark. |
| Certificate mismatch (wrong CN/SAN) | CNAME points to wrong ngrok edge region (us, eu, ap, etc.) | Run dig CNAME api.example.com — the target must match your ngrok edge region (e.g., us.ngrok.io, eu.ngrok.io). |
502 Bad Gateway on custom domain |
Local service not listening on the port ngrok forwards to | From the same machine running the ngrok agent: curl -v http://localhost:PORT. |
| TLS handshake timeout | Firewall or security group blocks ngrok edge IPs on port 443 | telnet <edge-ip> 443 from the agent host; check current ngrok IP ranges at ngrok firewall docs. |
Ordered Checks
- Verify domain status in dashboard. Go to Domains → your domain. Status must read Verified. If it shows Pending or Unverified, DNS verification is incomplete.
- Confirm CNAME target matches your edge region. Run
dig CNAME api.example.com. The answer should beus.ngrok.io,eu.ngrok.io,ap.ngrok.io, or another regional edge hostname that matches the region you selected when reserving the domain. - Test the local service directly. On the machine running the ngrok agent, run
curl -v http://localhost:<port>/health. Expect a200 OKresponse. If this fails, the service is down or listening on the wrong interface/port. - Inspect agent logs with debug output. Start the tunnel with:
Look for lines containingngrok http --domain=api.example.com 8080 --log=stdout --log-level=debugtls,handshake, orcertificate. A successful start showsstarted tunnelandregistered tunnelwith your custom domain. - Validate outbound firewall rules. ngrok edge IPs change periodically. Allow outbound TCP 443 to the CIDR ranges published in the ngrok firewall documentation. Static IP allowlists will break.
- Confirm authtoken is configured. Run
ngrok config check. It should report a valid token. If missing or expired, runngrok config add-authtoken <token>using the token from the dashboard → Your Authtoken.
Fixes Tied to Findings
- Domain unverified → Complete DNS verification: add the TXT record shown in the dashboard, wait for propagation (up to 48 hours), then click Verify in the dashboard.
- Wrong CNAME target → Update the DNS CNAME record to the correct regional edge (e.g.,
api.example.com CNAME us.ngrok.io). Propagation may take up to 48 hours. - Local service down or unreachable → Start the service on the correct port and bind to
0.0.0.0(not127.0.0.1). In containers, usehost.docker.internalor the container's actual IP. - Firewall blocking ngrok edges → Allow outbound TCP 443 to ngrok's published CIDR ranges. Do not hardcode individual IPs.
- Authtoken missing or expired → Run
ngrok config add-authtoken <token>from the same user context that runs the agent.
Escalation Criteria
Open an ngrok support ticket (paid plans) or file a GitHub issue (ngrok/ngrok) when:
- Domain shows Verified but the certificate still serves
*.ngrok.ioafter 30 minutes. - Agent logs show
tls: handshake failurewith a valid config and open firewall. - Custom domain works intermittently (flapping) across multiple edge regions.
- You need to migrate a reserved domain between accounts or regions — this requires support intervention and incurs ~5 minutes of downtime.
Verification Steps
After applying fixes, confirm the tunnel works end-to-end:
- Run the agent with info-level logging:
Confirm logs showngrok http --domain=api.example.com 8080 --log=stdout --log-level=infostarted tunnelandregistered tunnelwith your custom domain. - From a different network (e.g., mobile hotspot), run:
Expectcurl -v https://api.example.com/health200 OKwith a valid certificate (CN=api.example.com, issuer=Let's Encrypt or ngrok CA). - Check certificate transparency logs at crt.sh to confirm a valid cert was issued for your domain.
- Use OpenSSL to verify the TLS handshake and chain:
openssl s_client -connect api.example.com:443 -servername api.example.com </dev/null - In the ngrok dashboard Endpoints page, verify your custom domain shows green Online status and the correct upstream URL (e.g.,
http://localhost:8080).
Limitations and Practical Notes
- Custom domains require a paid ngrok plan (Basic or higher). Free plans do not support them.
- DNS propagation for TXT and CNAME changes can take up to 48 hours; dashboard Verified status may lag behind actual DNS availability.
- ngrok edge IPs rotate. Firewall rules using static IPs will break. Use the published CIDR ranges or allow all outbound 443.
- Binding a local service to
127.0.0.1prevents the ngrok agent (even on the same host) from reaching it vialocalhost. Bind to0.0.0.0or usehost.docker.internalin containers. - TLS termination occurs at the ngrok edge. The connection from edge to your local service is HTTP by default. Only use
ngrok http --scheme=httpsif your local service actually speaks HTTPS (rare). - Reserved domains are account-scoped and region-locked. Moving a domain to another region requires releasing and re-claiming it, causing ~5 minutes of downtime.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.