Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Scan for outdated or missing drivers - takes under a minute3Repair Windows errors before they cause bigger problemsRetry a failed PHP cURL request in application code: check whether curl_exec() returned false, save the cURL error details while the handle is open, and retry only within a finite policy. Treat HTTP status codes separately. A 404 response, for example, is not a cURL transfer failure by default. Before retrying a request that can change server state, make sure repeating it is safe.
First, distinguish a transfer failure from an HTTP error
With CURLOPT_RETURNTRANSFER enabled, curl_exec() returns the response body when the transfer succeeds and false when the transfer fails. A successful transfer can still return an HTTP error response: by default, cURL does not treat a status such as 404 as a transfer failure. That means an if (!$body) check is not enough to make the decision correctly. Check strictly for false, then inspect the HTTP status independently.
| Result | What to inspect | Retry decision |
|---|---|---|
curl_exec() returns false |
curl_errno() and curl_error(), read before closing the handle |
Consider retrying if the failure is transient, the request is safe to repeat, and the retry budget remains. |
curl_exec() returns a body |
HTTP status from curl_getinfo() |
Apply the endpoint’s status policy. An HTTP response is not automatically a cURL failure. |
curl_errno() returns zero when there was no cURL error; curl_error() returns an empty string when there was none. The error number is useful for programmatic decisions, while the message helps with logging and diagnosis. Do not discard the handle before collecting them.
A bounded PHP example for a GET-like request
This example retries transfer failures only. It makes three total attempts at most, gives each attempt a five-second connection timeout and a 15-second total timeout, and uses a short increasing delay between attempts. Those counts, timeouts, and delays are illustrative policy choices, not universal cURL recommendations. The example accepts only a 2xx HTTP response; other status codes throw an exception without being retried.
The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →#1 Best Overall
<?php
function getWithRetries(string $url, int $maxAttempts = 3): string
{
for ($attempt = 1; $attempt <= $maxAttempts; $attempt++) {
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_TIMEOUT => 15,
]);
$body = curl_exec($ch);
if ($body !== false) {
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
// Decide separately whether this HTTP status is acceptable.
if ($status >= 200 && $status < 300) {
return $body;
}
throw new RuntimeException("HTTP status {$status}");
}
$errno = curl_errno($ch);
$error = curl_error($ch);
curl_close($ch);
if ($attempt === $maxAttempts) {
throw new RuntimeException("cURL error {$errno}: {$error}");
}
// Example bounded delay; tune for the caller's latency budget.
usleep(100_000 * $attempt);
}
throw new RuntimeException('Request attempts exhausted');
}
try {
$body = getWithRetries('https://example.com/data');
// Use or decode $body here.
} catch (RuntimeException $e) {
// Log the failure and handle it at the appropriate application boundary.
error_log($e->getMessage());
}
Replace the example URL with the endpoint you intend to call. The handle is closed both after a successful transfer and after a transfer failure, and the error number and text are saved before closing. A status rejection is thrown after the handle has been closed. In production code, catch failures at the layer that can make a useful decision: for example, whether to report an upstream error, return a fallback, or surface a failure to the caller.
What this example does not do
- It does not retry HTTP responses such as 429 or 503. Add status retries only if the upstream endpoint’s behavior and your application’s requirements justify them.
- It does not enforce one wall-clock deadline across the entire retry operation. The per-attempt timeout limits one transfer; several attempts and delays can take longer than one timeout.
- It does not add jitter, parse
Retry-After, classify particular cURL error numbers, or handle multi-handle transfers. These require policy tailored to the service and caller’s latency budget. - It is for a GET-like operation, not a blanket rule for POST, payment, or other state-changing requests.
Decide which failures are eligible for another attempt
Transfer failures
A false result tells you the transfer failed, not that another attempt will fix it. Your application should decide whether a failure is plausibly transient and worth retrying. A persistent configuration problem, for example, will not be solved by sending the same request repeatedly. Keep the policy finite and preserve the last error for the caller or logs; do not retry forever or turn all errors into an unexplained empty response.
Rank #2
For simple policies, retrying all transfer failures a small, bounded number of times may be acceptable for a safe read request. For a more sensitive integration, classify failures deliberately and retry only those the upstream service or your operational requirements identify as appropriate. The PHP cURL references do not define a universal list of retryable errors.
HTTP responses
When a response body is returned, inspect its status using curl_getinfo($ch, CURLINFO_RESPONSE_CODE) before deciding what the application should do. The sample treats every non-2xx response as an exception but does not retry it. That is an example of explicit handling, not a universal status policy. A 404 can mean the requested resource does not exist; retrying it without a reason is unlikely to help. Other codes may call for endpoint-specific handling, but decide that based on the service contract rather than assuming every 4xx or 5xx response should be retried.
Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minutePC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11If you enable CURLOPT_FAILONERROR
CURLOPT_FAILONERROR changes cURL’s handling of HTTP response codes at or above 400. If you enable it, account for that behavior in your error reporting and retry logic: a result that would otherwise be handled as an HTTP response may be surfaced at the cURL layer. If your application needs to distinguish transport failures from HTTP statuses clearly, leaving this option off and checking the status explicitly is a straightforward approach.
Make sure repeating the request is safe
A retry sends another request to the server. For a read-only GET-like operation, repeating the request is often the intended behavior, but confirm the endpoint’s semantics rather than relying on the method name alone. A request that creates, charges, deletes, or otherwise changes state can succeed at the server even if the client fails to receive the response. Retrying it could repeat the side effect.
Rank #4
Before retrying a state-changing request, establish an idempotency strategy with the API or application. If the endpoint does not offer a way to safely identify duplicate attempts, do not apply the sample unchanged. Decide how to reconcile an uncertain outcome instead of assuming that a transfer error proves the server did nothing.
Set timeouts and an overall retry budget
Set both a connection timeout and a total timeout for each transfer. CURLOPT_CONNECTTIMEOUT bounds time spent establishing the connection; CURLOPT_TIMEOUT bounds the complete transfer. The connection time is included in the total timeout, so the total does not begin only after connection setup finishes.
Quick wins for a faster PC:
Repair Windows errors before they cause bigger problemsFix Now →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Clear out junk files and repair common Windows errorsFree Scan →A per-attempt timeout is not the same as a deadline for the whole operation. If your caller must finish by a particular time, calculate a total retry budget that includes all attempts and wait periods, and stop when that budget is exhausted. Otherwise, three attempts with a 15-second total timeout can consume materially more time than a caller with a short response deadline can tolerate. Choose attempt limits and delays to fit that caller’s latency budget.
Backoff, jitter, and service guidance
The sample’s usleep(100_000 * $attempt) is only a small illustrative increasing delay. It is not a recommended universal schedule. An integration may need a different backoff, jitter to avoid synchronized retry bursts, or respect for a service-provided retry delay. The official PHP and libcurl mechanics do not prescribe an attempt count, backoff algorithm, jitter policy, or retryable status list. Use the upstream API’s guidance and your own deadline and load constraints.
Troubleshoot common retry problems
- The code does not retry on a 404. By default, a 404 is an HTTP response, not a transfer failure. Inspect the response status and implement a separate status policy only if appropriate for the endpoint.
- The error number or message is empty. You may be reading diagnostics after a successful transfer, or after closing/discarding the handle. Read
curl_errno()andcurl_error()immediately whencurl_exec()returnsfalse, before closing it. - The caller still times out despite per-attempt timeouts. The timeout applies to one transfer, not the entire loop. Add an overall deadline that includes delays and subsequent attempts, and stop when it is reached.
- A request runs twice or creates duplicate data. A retry may repeat a server-side action even if the first response was lost. Remove the blanket retry for that operation until safe repeatability or an idempotency strategy is established.
- HTTP statuses appear as cURL failures. Check whether
CURLOPT_FAILONERRORis enabled. Its behavior for status codes at or above 400 needs to be included in your status and error policy. - Multi-handle diagnostics do not match the single-handle example. For multi-handle transfers, use the individual result returned by
curl_multi_info_read(); do not assume the single-handlecurl_errno()flow describes each transfer.
Or skip the browser setup
If your PHP task is to capture a clean screenshot of a web page rather than implement retries for a general HTTP API, ScreenshotNeo provides a website screenshot API and MCP server. Its cURL request is:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
See the ScreenshotNeo API documentation for request options. Cookie and consent banners are accepted like a visitor, then removed along with 60+ known consent platforms, newsletter popups, and chat widgets; each of those steps can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the page verdict and billing status in headers. Its MCP server provides take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 shots. Sign up for the free plan.
Frequently Asked Questions
Does this example retry HTTP 429 responses?
No. It retries transfer failures only. Whether to retry 429, and how to handle any service-provided delay, depends on the endpoint’s guidance and your application’s policy.
Can I use the same pattern with a POST request?
Not without first confirming that repeating the operation is safe. A transfer failure does not prove the server did not process the first request.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

