PHP API Request Times Out Even Though Endpoint Works

1
php


<?php if (is_user_logged_in());

?>

I am trying to make a PHP request to an external API, but the request periodically times out even though the API endpoint itself appears to be online and responding normally when I test it directly.

The problem does not happen on every request. Some requests complete in less than a second, while others wait until the timeout limit is reached.

I am trying to determine whether this is more likely to be caused by my PHP configuration, the remote API, DNS/network latency, or the way I am handling the request.

Current PHP code

php

<?php

$url = 'https://api.example.com/products';

$ch = curl_init();

curl_setopt_array($ch, [
    CURLOPT_URL => $url,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CONNECTTIMEOUT => 10,
    CURLOPT_TIMEOUT => 30,
    CURLOPT_FOLLOWLOCATION => true,
    CURLOPT_HTTPHEADER => <span class="text-token-text-primary cursor-text rounded-sm" data-placeholder-token="true">[
        'Accept: application/json',
    ]</span>,
]);

$response = curl_exec($ch);

if ($response === false) {
    echo 'cURL Error: ' . curl_error($ch);
} else {
    $data = json_decode($response, true);

    echo '<pre>';
    print_r($data);
    echo '</pre>';
}

curl_close($ch);

What I have already checked

  1. The API URL loads successfully when tested directly.
  2. PHP cURL is enabled on the server.
  3. Most requests work correctly.
  4. Increasing CURLOPT_TIMEOUT only makes failed requests take longer before returning an error.
  5. The server itself does not appear to be under unusually high CPU or memory usage.
  6. The issue happens intermittently rather than on every request.

Example error

code

cURL error 28:
Operation timed out after 30001 milliseconds
with 0 bytes received

What I am trying to figure out

What would be the best way to troubleshoot this?

Would you recommend checking:

  • DNS lookup time?
  • connection time separately from total request time?
  • remote server response headers?
  • PHP-FPM limits?
  • firewall or outbound connection rules?
  • retry logic?
  • API rate limiting?
  • logging detailed cURL timing information?

I would also like to know whether automatically retrying the request is considered a good approach, or whether that could make the problem worse if the remote API is already having trouble.

If retries are appropriate, what would be a reasonable retry strategy in PHP without causing duplicate requests or unnecessary load?

Adding CSS and HTML to test:

css

.testt {
}
html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>My First Webpage</title>
</head>
<body>
  <h1>Welcome to My Webpage</h1>
  <p>Your FAce Is COde!.</p>
</body>
</html>
Loading discussion…Preparing formatted code and rich content.
Your Answer

Respond directly to the original question. To respond to another answer, use Reply under that answer.

Have an answer?

Log in with your ProfoundSyntax account to participate.

Log In to Reply

2 Answers

Thread contextShowing the selected reply branch and its parent context.View full discussion
  1. 1
    Miles Rivera
    Miles Rivera V ✓ Accepted Answer
    @miles-rivera 0 rep
    Replying to @diamondj

    To isolate the cause of the intermittent 28‑error you can add a few diagnostics and then decide whether a retry policy is appropriate.

    1. Collect timing data for each request

    php

       $ch = curl_init($url);
       curl_setopt_array($ch, [
           CURLOPT_RETURNTRANSFER   => true,
           CURLOPT_CONNECTTIMEOUT   => 10,
           CURLOPT_TIMEOUT          => 30,
           CURLOPT_FOLLOWLOCATION   => true,
           CURLOPT_HTTPHEADER       => ['Accept: application/json'],
       ]);
    
       $response = curl_exec($ch);
    
       if ($response === false) {
           $error = curl_error($ch);
           $info  = curl_getinfo($ch);
           error_log("cURL error: $error");
           error_log("Connect time: {$info['connect_time']}s, Total time: {$info['total_time']}s");
       } else {
           // normal handling
       }
       curl_close($ch);

    * connect_time shows DNS + TCP handshake.
    * total_time is the full round‑trip.
    * pretransfer_time can reveal TLS negotiation delays.

    Store these values in a log or a database column so you can spot patterns (e.g., spikes after a certain hour).

    2. Check DNS resolution separately

    bash

       dig api.example.com +time=5

    If the lookup sometimes takes >5 s, the DNS server is the culprit. Switch to a more reliable resolver (e.g., 8.8.8.8) or enable CURLOPT_DNS_CACHE_TIMEOUT.

    3. Verify the remote server’s health
    * Use a monitoring tool (e.g., curl -w “%{http_code} %{time_total}n” -o /dev/null -s https://api.example.com/products) to see if the endpoint itself sometimes returns 5xx or 429.
    * Look at the API provider’s status page or rate‑limit headers (X-RateLimit-Remaining, Retry-After).

    4. Inspect server‑side limits
    * PHP‑FPM workers: pm.max_children, pm.max_requests.
    * Web‑server timeouts: ProxyTimeout (Apache) or proxy_read_timeout (NGINX).
    * Firewall rules: iptables -L -n or cloud provider outbound rules.

    5. Implement a safe retry strategy
    * Only retry on idempotent verbs (GET, HEAD).
    * Use exponential back‑off with jitter:

    php

         $maxRetries = 3;
         $delay = 1; // seconds
    
         for ($i = 0; $i < $maxRetries; $i++) {
             $response = curl_exec($ch);
             if ($response !== false) break;
    
             $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
             if ($code == 429 || $code == 503) {
                 // Respect Retry-After if present
                 $retryAfter = curl_getinfo($ch, CURLINFO_HEADER_OUT);
                 // parse header if needed
             }
    
             sleep($delay + rand(0, 1)); // jitter
             $delay *= 2; // exponential
         }

    * Log each retry attempt and the reason for failure.
    * Do not retry on client errors (4xx other than 429) or on network failures that are not transient.

    6. Avoid duplicate side‑effects
    * For non‑idempotent operations (POST, PUT, DELETE) either do not retry or use a unique request ID that the API can recognize to deduplicate.

    7. Optional: use curl_multi
    If you need to send many requests concurrently, curl_multi can keep connections alive and reduce DNS lookups, but it adds complexity.

    Summary

    1. Log connect_time, total_time, and error codes.
    2. Verify DNS and remote server health.
    3. Check PHP‑FPM and web‑server timeouts.
    4. If retries are needed, limit them to idempotent calls, use exponential back‑off, and respect rate‑limit headers.

    These steps should help you pinpoint whether the problem is local, network‑wide, or on the API side, and give you a controlled way to handle transient failures.

    Helpful 1