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

Set the timeout where PHP is actually waiting. A remote HTML-to-PDF service needs an HTTP-client timeout; a renderer started on the same machine needs a child-process timeout. For Symfony HttpClient, use timeout to limit idle periods and max_duration to cap the complete request. Then check PHP, your web server or proxy, queue worker, and the PDF service for separate deadlines.

First identify which operation is hanging

“HTML-to-PDF request” can describe two different execution paths:

  • Remote conversion: PHP sends HTML or a URL to an HTTP API and waits for the response. Configure the HTTP client’s limits.
  • Local conversion: PHP launches Chromium, wkhtmltopdf, Gotenberg, or another executable and waits for a child process. Configure the process timeout.

Changing one limit does not change the other. A remote API can also have its own rendering deadline, while PHP and a reverse proxy can terminate the request before your client timeout is reached.

Symfony HttpClient: idle timeout versus total duration

Symfony’s documentation describes timeout as an inactivity limit: “Timeouts control how long one is willing to wait while the HTTP transaction is idle.” Data can continue arriving for longer than this value if no pause exceeds the limit. If you omit the option, PHP’s default_socket_timeout applies. The current Symfony example uses 2.5 seconds as an illustration, not as a PDF-generation recommendation. See the Symfony HTTP Client documentation.

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

Use max_duration when the requirement is a wall-clock ceiling for the complete request and response. A conversion that continually streams small pieces of data can exceed an idle timeout without violating it; max_duration stops that request once its total budget is exhausted.

Basic remote conversion code

<?php

use SymfonyComponentHttpClientHttpClient;
use SymfonyContractsHttpClientExceptionTransportExceptionInterface;

$client = HttpClient::create();
$pdfServiceUrl = 'https://pdf.example.test/convert';

try {
    $response = $client->request('POST', $pdfServiceUrl, [
        'json' => [
            'html' => '<h1>Invoice 123</h1>',
        ],
        // Maximum idle interval while the HTTP transaction is active.
        'timeout' => 10.0,
        // Maximum elapsed time for this complete request and response.
        'max_duration' => 45.0,
    ]);

    // Symfony responses are lazy: transport work can fail here, not only above.
    $status = $response->getStatusCode();
    $pdf = $response->getContent();
    file_put_contents(__DIR__ . '/invoice.pdf', $pdf);
} catch (TransportExceptionInterface $e) {
    error_log('PDF transport timed out or failed: ' . $e->getMessage());
    http_response_code(504);
    echo 'The PDF service did not respond within the configured time.';
}

The values above are application choices. Select them from observed conversion latency, HTML complexity, the service’s limits, and the caller’s deadline. Do not copy the illustrative 2.5-second value as a universal setting.

Limit connection establishment separately

DNS resolution, TCP connection, and TLS negotiation can deserve a shorter budget than rendering. Current Symfony documentation lists max_connect_duration for that purpose and marks it as introduced in Symfony 8.1. Verify your installed Symfony version before adding it:

$response = $client->request('POST', $pdfServiceUrl, [
    'timeout' => 10.0,
    'max_connect_duration' => 5.0, // Symfony 8.1 and later, where supported
    'max_duration' => 45.0,
]);

If your version does not expose this option, use that version’s documentation rather than guessing an option name. A connection cap cannot fix a renderer that connects successfully and then stalls.

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.

Local renderers: set the child-process timeout

When PHP runs a binary, Symfony Process controls the wait. The Symfony Process documentation states that the default process timeout is 60 seconds. Calling setTimeout() changes it; reaching the limit throws ProcessTimedOutException. These settings are independent of an HTTP client’s timeout. Refer to the Symfony Process documentation.

<?php

use SymfonyComponentProcessProcess;
use SymfonyComponentProcessExceptionProcessTimedOutException;

$process = new Process([
    '/usr/bin/chromium',
    '--headless',
    '--disable-gpu',
    '--print-to-pdf=/tmp/invoice.pdf',
    '/var/www/app/invoice.html',
]);
$process->setTimeout(120.0);

try {
    $process->mustRun();
    if (!is_file('/tmp/invoice.pdf')) {
        throw new RuntimeException('Renderer exited without producing a PDF.');
    }
} catch (ProcessTimedOutException $e) {
    error_log('Renderer exceeded its 120-second process budget.');
    // Remove partial output and return a controlled failure to the caller.
    @unlink('/tmp/invoice.pdf');
    http_response_code(504);
} catch (Throwable $e) {
    error_log('Renderer failed: ' . $e->getMessage());
    http_response_code(500);
}

For asynchronous execution, Symfony Process requires regular calls to checkTimeout(); otherwise your application may not notice the deadline promptly. A larger process timeout also cannot repair invalid command-line arguments, missing fonts, blocked assets, or a browser waiting forever for page activity.

Make the timeout fit the whole request budget

Choose a deadline from the outside in. The user-facing request, queue job, or webhook has a finite budget that must include conversion, retries, and any delay between attempts.

  1. Find the outer deadline. Check the PHP execution setting, web-server and reverse-proxy upstream timeouts, load balancer, queue-worker visibility timeout, and the remote PDF service’s documented limit. PHP’s connection behavior when its time limit is reached is described in the PHP connection-handling documentation; the exact limits vary by deployment.
  2. Reserve time for application work. Leave room to validate input, upload HTML, store the PDF, and send the response after conversion.
  3. Set the per-attempt idle limit. This catches a silent socket without cutting off a healthy stream of data.
  4. Set the total duration. Keep max_duration below the caller’s remaining budget.
  5. For local rendering, set the process limit below the worker or HTTP deadline so PHP can terminate cleanly and report an error.

For example, if a queue job allows 90 seconds, a 45-second conversion attempt may be reasonable only if the job also has enough time for preparation and cleanup. There is no documented universal PDF timeout; measure your own normal and worst-case documents.

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

Retries: per-attempt limits are not a total limit

Retries multiply elapsed time. If an attempt can run for 45 seconds and two retries are allowed, the total can approach 135 seconds before adding backoff delays. Symfony 5.x documentation describes retries for selected status codes with exponential delay, but retry behavior and supported methods depend on the Symfony version; consult the Symfony 5.x HttpClient documentation for the version you run.

  • Retry transient connection failures and selected server responses, not malformed HTML or authentication errors.
  • Use an overall deadline in your job or controller so retries cannot outlive the caller.
  • Make the conversion operation idempotent, or attach a request identifier so a late first attempt cannot create duplicate records.
  • Log attempt number, elapsed time, timeout type, HTTP status, and service request ID when available.

Browser readiness can be a separate wait

A converter may wait for fonts, images, JavaScript, or a browser network-idle event before producing the PDF. Gotenberg’s Chromium conversion documentation warns that waiting for all connections to close can be unsuitable for pages using long-polling or analytics connections. See Gotenberg’s HTML-to-PDF documentation.

Align the service-side readiness rule with the page:

  • Use a less strict readiness condition for applications that keep WebSockets, long-polling, or telemetry connections open.
  • Prefer a specific “page is ready” selector or application signal when the converter supports it.
  • Ensure external fonts and images are reachable from the renderer’s network and that authenticated assets have valid credentials.
  • Set a client timeout long enough for the chosen readiness condition, but retain a hard total-duration cap.

A client-side timeout only stops waiting; it cannot make a service-side network-idle condition succeed. If the page never reaches readiness, investigate the page and converter logs rather than continually increasing PHP’s limit.

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

Diagnose common timeout and PDF failures

Symptom Likely cause Action
Exception appears at getContent(), not request() Symfony response laziness deferred network activity Keep the try/catch around request creation, status/header access, and body consumption.
Request fails after a quiet period Idle timeout was reached Inspect service logs and asset loading; increase the idle value only if the pause is expected.
Request runs too long despite continuous output No total-duration cap Add max_duration and enforce an outer application deadline.
“Process timed out” after about one minute Symfony Process’s documented 60-second default Call setTimeout() with a budget appropriate to the document and worker.
PDF service reports network-idle timeout Persistent browser connections prevent the selected readiness state Change readiness settings or page behavior; do not rely only on a larger client timeout.
PHP returns 504 before the client limit Web server, proxy, queue, or PHP runtime deadline is shorter Trace each layer and make inner budgets finish before the outer deadline.
Repeated attempts create duplicate PDFs Retry is not idempotent Use an idempotency key or reconcile outputs by a unique job ID.
Renderer exits successfully but no usable PDF exists Bad input path, missing assets, permissions, or partial output Check exit output, verify the file, validate its size and PDF signature, and remove partial files.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Measure before changing numbers

Record separate timings for connection setup, upload, browser readiness, conversion, download, and post-processing. Compare successful and failed jobs by document size, number of external assets, JavaScript usage, and renderer version. This shows whether you need a longer idle interval, a larger total budget, or a page-level fix.

Use a correlation ID in application logs and pass it to the remote service when supported. Capture the exact timeout option, Symfony version, retry count, HTTP status, and exception class. Never log authorization headers or document contents. For asynchronous jobs, persist the deadline and stop polling when it expires instead of allowing an orphaned renderer to consume workers.

Or skip the browser setup

If your goal is a clean capture of a web page rather than maintaining a browser and PDF renderer yourself, ScreenshotNeo provides a website screenshot API and MCP server. Before capture it accepts cookie or consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and each response reports the result through X-Page-Verdict and X-Billed headers. It can return PNG, JPEG, WebP, or PDF, and its MCP tools (take_screenshot, get_page_info, and capture_pdf) work with Claude, Cursor, and other MCP clients.

The API accepts full-page capture, lazy-image loading, element selectors, dark mode, device presets or custom viewports, retina scale, PDF paper and margin settings, custom CSS and JavaScript, clicks, waits, blocked requests, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, caching TTLs, signed image links, asynchronous webhooks, bulk capture of up to 100 URLs per call, usage reporting, and an OpenAPI specification. Existing parameter names used by other screenshot APIs also work, which can simplify migration. Details and option names are in the ScreenshotNeo documentation.

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

One-call examples

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
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)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

ScreenshotNeo’s Free plan includes 1,000 shots per month with no card. Paid plans start at $5 for 3,000 shots; every feature is on every plan, and yearly billing gives two months free. Create a free ScreenshotNeo account to try it without a card.

Frequently Asked Questions

Should I use only timeout or only max_duration in Symfony HttpClient?

Use both when you need protection from a silent connection and a hard wall-clock ceiling. They control different conditions: inactivity versus total transaction time.

Does increasing PHP’s execution limit increase a PDF service’s own limit?

No. PHP, the HTTP client, the renderer, the proxy, and the remote service can each enforce separate deadlines. Change the layer that is ending the wait.

Why can a page that works in a browser still time out during PDF conversion?

The renderer may lack authentication, network access, fonts, or a readiness signal, or the page may keep persistent connections open. Inspect converter and browser logs before increasing the timeout.

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

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.