cURL error 28: Operation timed out after 10000 milliseconds
Stefan Bogdanescu
Founder & Senior Architect · 2026-06-29
Decoding cURL Error 28 in Guzzle: Why Your HTTP Requests Time Out
As developers working with external APIs, managing network requests is often the most frustrating part of the process. You might successfully test an endpoint using tools like Postman, only to encounter inexplicable failures when implementing the same logic in your backend code using libraries like Guzzle. One very common culprit is the dreaded cURL error 28: Operation timed out after 10000 milliseconds.
This post will dive deep into why this timeout happens specifically with Guzzle, how it differs from manual testing, and the practical steps you can take to diagnose and resolve these frustrating network communication issues.
Understanding the Timeout: What is cURL Error 28?
The error message cURL error 28: Operation timed out after 10000 milliseconds originates from the underlying cURL library that Guzzle uses to make HTTP requests. In simple terms, this means that the client (your PHP script using Guzzle) sent a request to the server but did not receive any data back within the specified time limit (in your case, 10 seconds).
While Postman might appear to work fine, it often handles connection retries, connection pooling, and underlying network optimizations differently than a raw HTTP client implementation in PHP. The timeout usually indicates one of three main issues:
- Server Latency: The remote server is taking too long to process the request and send the response.
- Network Congestion: There are intermittent issues or bottlenecks in the network path between your server and the API host.
- Incorrect Configuration: The timeout setting in Guzzle is too aggressive for the specific operation being performed.
Why Guzzle Fails When Postman Succeeds
The discrepancy between Postman success and Guzzle failure often points to differences in how connection management is handled. When you use Postman, it manages the TCP handshake and subsequent data flow seamlessly. When Guzzle executes the request, especially under load or with specific network configurations, these nuances can cause the operation to stall before a response is fully received.
The provided code snippet shows a reasonable attempt at setting a timeout:
$client = new \GuzzleHttp\Client([
'timeout' => 10 // Setting a 10-second limit
]);
// ... subsequent post request
While setting the timeout is crucial, it only dictates the maximum time Guzzle will wait for any response. If the server is slow but still sending data intermittently, this timeout can be hit prematurely.
Practical Solutions and Best Practices
To tackle this issue effectively, we need to adjust our strategy beyond just setting a fixed timeout. Here are the recommended steps:
1. Increase Timeout Gradually
If you suspect simple latency, try increasing the timeout value slightly. However, be cautious; excessively long timeouts can mask underlying performance problems or cause unnecessary resource usage on your server.
Try setting it higher initially to see if the request completes successfully:
$client = new \GuzzleHttp\Client([
'timeout' => 30 // Try increasing this to 30 seconds
]);
2. Implement Retries with Exponential Backoff
Network operations are inherently unreliable. A single timeout is often a temporary hiccup. The most robust solution is to implement automatic retries with exponential backoff. This tells Guzzle to wait a short period, retry the request, and increase the wait time if it fails again. This pattern is essential for building resilient microservices, much like the architectural principles discussed in frameworks like Laravel, where reliability is paramount.
You can achieve this by using middleware or custom handlers:
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Client;
try {
$response = $client->post(url('oauth/token'), [ /* ... params */ ]);
} catch (ConnectException $e) {
// Implement retry logic here instead of just exiting
// For production code, use a dedicated retry library or custom middleware.
throw new \Exception("Request failed after retries: " . $e->getMessage());
}
3. Inspect the Server and Network
If increasing the timeout doesn't fix the issue, the problem likely lies outside your immediate application code:
- Check API Health: Verify that the OAuth server itself is operational and not overloaded. Use external tools to check API response times directly.
- Examine Logs: Check your web server or application logs for any errors related to DNS resolution or connection refusals, which can sometimes manifest as timeouts.
- Firewalls/Proxies: If you are operating behind a corporate firewall or proxy, ensure that outbound traffic to the API endpoint is not being blocked or throttled by intermediate network devices.
Conclusion
The cURL error 28 when using Guzzle often signals a gap between idealized testing environments and real-world network performance. By moving beyond simple hard timeouts and adopting resilient patterns like exponential backoff retries, you transform your application from brittle to robust. Always treat external API communication as an unreliable channel, and build your code to expect failure gracefully, ensuring that even when things go wrong, your system remains stable.