Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

Set Guzzle’s timeout request option to a positive number of seconds. It limits the complete request. Use it on one request for a local override, or set it when constructing the client for a default. Catch TransferException because a timeout normally produces no HTTP response.

The direct implementation

This request fails through Guzzle’s transfer-exception path if the operation has not completed within five seconds:

<?php

use GuzzleHttpClient;
use GuzzleHttpExceptionTransferException;

$client = new Client();

try {
    $response = $client->request('GET', 'https://example.com/api', [
        'timeout' => 5.0,
    ]);

    echo $response->getBody();
} catch (TransferException $e) {
    // Log the failure and return an application-specific error.
    error_log('HTTP request failed: ' . $e->getMessage());
}

The value is expressed in seconds, and a floating-point value such as 0.5 is valid. The documented default for timeout is 0, which means no total deadline. A finite value is usually safer for web requests, queue workers and command-line jobs whose callers have a latency budget.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Understand Guzzle’s three timeout scopes

These options do not measure the same part of a transfer. Select the narrowest scope that matches the failure you need to prevent.

Option Scope Documented default Important qualification
timeout The complete request 0 (indefinite) Use a positive number to impose a total cap. It can be set per request or as a client default.
connect_timeout Connection establishment 0 (indefinite) Support depends on the active transfer handler; the built-in cURL handler supports it.
read_timeout One read from a streamed response body Not a total-request setting It applies when stream is enabled and does not cap the entire download.

A total timeout and a connection timeout can be used together. The connection limit controls how long connection setup may take, while timeout remains the overall ceiling. A custom handler must actually implement the options you rely on; Guzzle passes transfer settings to the handler rather than enforcing every setting itself.

Set a default for every request

Pass timeout to the client constructor when all requests made by that client should share a baseline deadline:

<?php

use GuzzleHttpClient;

$client = new Client([
    'timeout' => 5.0,
]);

$response = $client->request('GET', 'https://example.com/api');

Guzzle clients are immutable. Configure a different default by constructing another client; do not expect to mutate the existing client’s defaults after creation. A request-level option can override the client default for an operation that legitimately needs more or less time:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
$response = $client->request('GET', 'https://example.com/slow-report', [
    'timeout' => 30.0,
]);

Keep separate clients when different parts of an application have clearly different latency budgets. This makes the policy visible at construction and avoids accidentally relaxing a short deadline for unrelated calls.

Add a connection bound when connection setup is the problem

Use connect_timeout when a request can spend too long establishing a network connection. It is measured in seconds:

$client = new GuzzleHttpClient([
    'timeout' => 10.0,
    'connect_timeout' => 2.0,
]);

The stable documentation identifies the built-in cURL handler as supporting connect_timeout. If your application selects a custom handler, verify that handler’s transfer-option support before depending on the setting. A connection timeout is not a replacement for a total timeout: a server can accept a connection and then take too long to produce the response.

Use read_timeout only for streamed bodies

For a streamed response, read_timeout limits an individual read operation. It is useful when your code consumes the body incrementally, but it does not define how long the complete request or download may take.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
<?php

use GuzzleHttpClient;
use GuzzleHttpExceptionTransferException;

$client = new Client();

try {
    $response = $client->request('GET', 'https://example.com/large-file', [
        'stream' => true,
        'timeout' => 120.0,
        'read_timeout' => 10.0,
    ]);

    $body = $response->getBody();
    while (!$body->eof()) {
        $chunk = $body->read(8192);
        if ($chunk !== '') {
            // Process or write this chunk.
        }
    }
} catch (TransferException $e) {
    error_log('Streaming request failed: ' . $e->getMessage());
}

Here, timeout remains the whole-operation limit, while each blocking read has its own limit. Without stream => true, read_timeout does not describe the normal buffered-response path.

Choose a value from the caller’s latency budget

Guzzle’s documentation defines the units and scopes but does not prescribe one universally correct number. Set the deadline from the surrounding operation:

  • For a browser-facing request, leave enough time for a normal response while protecting the page’s response-time budget.
  • For a queue job, include the job’s visibility or worker deadline so a timed-out attempt can finish before the job is redelivered.
  • For a batch or report download, use a larger total value and consider streaming rather than buffering the entire body.
  • For a connection to an optional dependency, use a short connection limit so an unavailable host does not consume the whole application deadline.

A value of 0 is unbounded. That may be appropriate for a deliberately long-lived operation, but it means a stalled transfer can wait indefinitely. Use a positive value whenever the caller requires a finite cap.

Handle failures correctly

Catch a suitable Guzzle transfer exception at the application boundary. A timeout is a transfer failure, not an HTTP status such as 408 or 504, and there may be no response object to inspect. Convert it into the error format your application exposes, and log enough context to identify the operation and configured deadline.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
try {
    $response = $client->request('GET', $url, ['timeout' => 8.0]);
    $status = $response->getStatusCode();
} catch (GuzzleHttpExceptionTransferException $e) {
    // The request may have failed before an HTTP response existed.
    return ['ok' => false, 'error' => 'upstream_unavailable'];
}

Retries need an explicit policy. Retry only operations that are safe to repeat, or use the upstream service’s idempotency mechanism for operations that create or change data. A timeout leaves the outcome unknown: the remote server might have processed the request even though the client stopped waiting. Bound the number of attempts and include backoff so a slow dependency is not amplified into a retry storm.

Keep TLS verification enabled

Timeout configuration does not require changing certificate verification. Guzzle enables the verify option by default, and its documentation warns that disabling verification is insecure. Keep verification enabled while diagnosing slow connections or transfers; setting verify => false is not a valid timeout workaround.

Common mistakes and fixes

The request still waits forever

  • Cause: The effective timeout is 0, or the option was placed on a different request/client than the one being used.
  • Fix: Set a positive value on the actual request, or construct the client with a finite default. Log the selected policy at the boundary where the request is made.

You catch an HTTP exception but not the timeout

  • Cause: A timeout can occur before any HTTP response exists.
  • Fix: Catch TransferException (or an appropriate subclass) around the request and handle the failure without assuming a status code.

connect_timeout appears ineffective

  • Cause: The active handler does not support that transfer option, or the delay occurs after connection establishment.
  • Fix: Confirm the handler configuration and distinguish connection delay from slow response or body reads. Keep a total timeout as the final guard.

read_timeout has no effect

  • Cause: The response is not being streamed.
  • Fix: Enable stream => true and read the body incrementally. Use timeout when you need a complete-operation limit.

A retry duplicated a write

  • Cause: The client timed out after the server received the request, so the retry submitted the same operation again.
  • Fix: Restrict automatic retries to idempotent operations or send an idempotency key understood by the API. Record the attempt outcome as unknown when necessary.

Someone disabled certificate checks while debugging

  • Cause: A TLS failure was mistaken for a timeout problem.
  • Fix: Restore the default verification behavior and diagnose certificate, hostname or trust-store issues separately.

Test the policy before relying on it

  1. Exercise a successful endpoint and confirm the request completes under the configured deadline.
  2. Use a controlled endpoint or local test server that deliberately delays connection, headers or body reads. Test each phase separately rather than treating every delay as the same failure.
  3. Record elapsed time, the option values, handler choice and exception message. A measured duration close to the configured value indicates the deadline fired; a much shorter failure may indicate DNS, TLS or another transfer error.
  4. Test the retry path with an idempotent operation and verify that your application returns a stable error when all attempts fail.
  5. For streamed downloads, pause between body chunks and confirm that individual read failures and the overall deadline are handled as separate conditions.

Do not infer a universal “correct” timeout from one test. Production latency varies by operation and dependency, so review the value against the caller’s deadline and the service’s normal response characteristics.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

If your testing or automation task also needs a clean screenshot of a URL, ScreenshotNeo provides a single HTTP call instead of maintaining browser-launch code. It accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups and chat widgets. Bot checks, blank pages, failed loads, timeouts and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers. Its MCP server exposes take_screenshot, get_page_info and capture_pdf to Claude, Cursor and other MCP clients.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

See the ScreenshotNeo API documentation for authentication and options. A one-call example in PHP is:

<?php

$params = http_build_query([
    'access_key' => 'YOUR_API_KEY',
    'url' => 'https://stripe.com',
]);

$data = file_get_contents('https://api.screenshotneo.com/v1/shot?' . $params);
file_put_contents('shot.webp', $data);

The equivalent cURL command is:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Python:

import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)

Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

Every feature is available on every plan: the free plan includes 1,000 screenshots per month with no card, and paid plans start at $5 for 3,000. Create a free ScreenshotNeo account to try it.

Final checklist

  • Set a positive timeout in seconds for a finite total deadline.
  • Use a constructor default for a client-wide policy and a request option for an exception.
  • Add connect_timeout only when connection setup needs its own bound and the handler supports it.
  • Use read_timeout with streamed responses; it limits individual reads, not the whole request.
  • Catch TransferException, because a timeout may provide no HTTP response.
  • Keep TLS verification enabled and make retries safe for the operation being repeated.

Frequently Asked Questions

Can a timed-out request still have been processed by the server?

Yes. The timeout limits how long the client waits; it does not prove that the remote server stopped work. Treat the result of a timed-out write as uncertain and use idempotency controls before retrying.

Should I use one timeout for every endpoint?

Not necessarily. Set each client or request’s deadline from the caller’s latency budget and the operation’s behavior; a streamed report and a short dependency lookup usually need different policies.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Does a timeout change Guzzle’s certificate validation?

No. Timeout options and TLS verification are separate. Leave verification enabled while diagnosing transfer timing.

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.