signalapp/Signal-Server · error · IOException
failed to make http request to Cloudflare Turn
Error message
failed to make http request to Cloudflare Turn: {} What it means
This is the request-transport failure branch of the same Cloudflare Turn credential fetch. The CompletableFuture from HttpClient.sendAsync is joined; if the underlying request failed (connection error, timeout, TLS problem), join() wraps it in a CompletionException, which is unwrapped and rethrown as an IOException. The log line records only e.getMessage() of the wrapper.
Solutions
- Check outbound connectivity from the server to the Cloudflare Turn endpoint (curl the URL from the host).
- Inspect ExceptionUtils.unwrap(e) in the thrown IOException for the root cause (ConnectException, HttpTimeoutException, SSLHandshakeException).
- Increase HttpClient connect/request timeouts if the failure is a timeout.
- Add bounded retries with backoff for transient network errors before surfacing the IOException to callers.
Example fix
// before
response = cloudflareTurnClient.sendAsync(req, HttpResponse.BodyHandlers.ofString()).join();
// after: tolerate transient network failures
for (int attempt = 0; attempt < 3; attempt++) {
try {
response = cloudflareTurnClient.sendAsync(req, HttpResponse.BodyHandlers.ofString()).join();
break;
} catch (CompletionException e) {
if (attempt == 2) throw new IOException(ExceptionUtils.unwrap(e));
Uninterruptibles.sleepUninterruptibly(1L << attempt, TimeUnit.SECONDS);
}
} Defensive patterns
Strategy: retry
Validate before calling
// pre-call reachability probe (startup health check)
HttpRequest probe = HttpRequest.newBuilder(URI.create(cloudflareTurnUrl)).method("HEAD", HttpRequest.BodyPublishers.noBody()).build();
boolean reachable = cloudflareTurnClient.sendAsync(probe, HttpResponse.BodyHandlers.discarding()).isDone(); Try / catch
try {
creds = manager.retrieveFromCloudflare(realm, keyId, ttlSeconds);
} catch (IOException e) {
Throwable root = ExceptionUtils.getRootCause(e);
if (root instanceof HttpTimeoutException || root instanceof ConnectException) {
// retry with backoff or serve cached credentials
}
} Prevention
- Configure explicit HttpClient connect and request timeouts and alert on their breach.
- Ensure egress firewall/proxy rules allow the Cloudflare Turn endpoint from production hosts.
- Wrap the join() call with bounded retries for transient CompletionExceptions.
When it happens
Trigger: cloudflareTurnClient.sendAsync(...).join() throws CompletionException because the HTTP call could not complete: DNS failure, connection refused/timeout, TLS handshake failure, or an interrupted join.
Common situations: Egress network restrictions from the server, Cloudflare endpoint unreachable, misconfigured proxy or firewall, DNS resolution problems, or overly short HttpClient connect timeouts.
Related errors
- registration service unavailable
- Registration service failure
- 503 Service Unavailable
- use websockets
- Response body was below minimum
AI-assisted analysis of signalapp/Signal-Server@100ab61c82 (2026-09-09).
Data as JSON: /api/errors/7e081a5c102ef600.
Report an issue: GitHub.
Appendix: source
Thrown at service/src/main/java/org/whispersystems/textsecuregcm/auth/CloudflareTurnCredentialsManager.java:138
turnHostname = this.cloudflareTurnHostname;
}
try {
cloudflareTurnComposedUrls = dnsNameResolver.resolveAll(turnHostname).get().stream()
.map(i -> switch (i) {
case Inet6Address i6 -> "[" + i6.getHostAddress() + "]";
default -> i.getHostAddress();
})
.flatMap(i -> turnUrlsWithIps.stream().map(u -> u.formatted(i)))
.toList();
} catch (Exception e) {
throw new IOException(e);
}
final HttpResponse<String> response;
try {
response = cloudflareTurnClient.sendAsync(getCredentialsRequest, HttpResponse.BodyHandlers.ofString()).join();
} catch (CompletionException e) {
logger.warn("failed to make http request to Cloudflare Turn: {}", e.getMessage());
throw new IOException(ExceptionUtils.unwrap(e));
}
if (response.statusCode() != Response.Status.CREATED.getStatusCode()) {
logger.warn("failure request credentials from Cloudflare Turn (code={}): {}", response.statusCode(), response);
throw new IOException("Cloudflare Turn http failure : " + response.statusCode());
}
final CloudflareTurnResponse cloudflareTurnResponse = SystemMapper.jsonMapper()
.readValue(response.body(), CloudflareTurnResponse.class);
return new TurnToken(
cloudflareTurnResponse.iceServers().username(),
cloudflareTurnResponse.iceServers().credential(),
clientCredentialTtl.toSeconds(),
turnUrls,
cloudflareTurnComposedUrls,
turnHostnameView on GitHub (pinned to 100ab61c82)