An API request normally finishes in a second, then intermittently fails with cURL error 28. Increasing the timeout is tempting. But a slow DNS lookup and a server that accepts a connection without replying are different problems. Let's find the waiting stage before giving the request more time.
Error 28 means a cURL operation reached a time limit. The number alone does not identify the slow component. An HTTP 500 response is different: a server replied with an application error. A certificate trust failure belongs to another investigation, covered in the cURL error 60 guide.
Connection time and total time have different budgets
CURLOPT_CONNECTTIMEOUT limits connection establishment, including name resolution and protocol negotiation such as TLS. TLS is the encrypted connection used by HTTPS. CURLOPT_TIMEOUT covers the entire operation, including that connection phase. A 30-second connection allowance cannot override a 10-second total deadline. The connection timeout documentation explains how the limits interact.
This example requires PHP's cURL extension. Replace the URL with a small test endpoint you control. It makes a GET request; do not repeatedly call a payment or record-creation endpoint while investigating timings.
<?php
$ch = curl_init('https://example.com/');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_TIMEOUT => 15,
CURLOPT_FOLLOWLOCATION => false,
]);
$body = curl_exec($ch);
$errno = curl_errno($ch);
$error = curl_error($ch);
$info = curl_getinfo($ch);
printf("curl_errno=%d http=%d\n", $errno, $info['http_code']);
printf("dns=%.3f connect=%.3f tls=%.3f first=%.3f total=%.3f\n",
$info['namelookup_time'],
$info['connect_time'],
curl_getinfo($ch, CURLINFO_APPCONNECT_TIME),
$info['starttransfer_time'],
$info['total_time']
);
if ($body === false) {
fwrite(STDERR, $error . PHP_EOL);
exit(1);
}
Use this as a terminal diagnostic script. STDERR is the terminal's error output stream. The response body is deliberately not printed because the timings are what we need. Inside a web application, handle diagnostic details in appropriate application logs rather than displaying them to visitors.
Read the numbers as milestones
These timings generally count from the start of the request to a particular milestone. They are not five independent stopwatches. dns marks completed name resolution, connect the established connection, tls the completed encrypted handshake, and first receipt of the first response byte. total covers the operation. First-byte time includes response preparation; it is not an isolated measurement of application execution. The reference definitions for DNS, TLS and first byte are useful when interpreting a capture.
| Observation | Next place to look |
|---|---|
| Name resolution takes most of the time | The application's resolver and the domain's DNS response |
| DNS finishes but connection establishment fails | Destination port, egress rules, routing and address family |
| TLS finishes but the first byte is late | Upstream application logs, queues and intervening proxies |
| First byte arrives quickly but completion is slow | Response size, transfer speed and streamed response behavior |
A zero value can mean the request never reached that milestone, not that the stage completed instantly. Connection reuse, proxies and protocol differences also affect interpretation. Compare several small requests rather than diagnosing the entire system from one sample.
Why a working shell request is not the whole test
The shell's curl binary and PHP's extension can use different library builds, proxy settings or execution environments. Being on the same host does not make them equivalent. Note the user, container and web-versus-terminal context. For a WordPress failure, also distinguish a direct request from the application's HTTP client: plugins or other request handling can introduce different behavior.
A larger deadline makes sense only after you understand normal response time. A timeout does not prove that the remote side did nothing. For state-changing calls, use the API's mechanism for preventing duplicate operations before adding automatic retries. After changing a setting or fixing a dependency, repeat the same request in the same environment and compare the same milestones. That shows which part actually improved.