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

The right timeout depends on which part of PDF generation is slow. If you use Grover, set its convert_timeout for PDF rendering, and configure request_timeout or launch_timeout separately when fetching content or starting the browser is the bottleneck. If you use Wicked PDF or PDFKit, the renderer is the separate wkhtmltopdf process; a Ruby timeout around a wrapper call is not, by itself, a dependable hard deadline for that process.

First identify what you need to time out

“PDF generation timed out” can describe several different failures: your application may be slow building the HTML, a browser may take too long to launch, the renderer may wait on page assets, the conversion itself may be slow, or a web request may exceed a server or proxy deadline while work continues elsewhere. Those clocks are related, but they are not interchangeable.

Measure HTML construction and renderer execution separately. Record which renderer and version ran, how long each stage took, and whether the process completed. Then set a limit on the stage that is actually consuming time. A single generous timeout can conceal the cause without fixing it.

  • Grover: browser launch, content requests, and PDF conversion have distinct options.
  • Wicked PDF or PDFKit: the Ruby gem invokes wkhtmltopdf, so the wrapper call, child process, web request, and background job can have separate deadlines.
  • Rails or another web stack: an HTTP server or reverse proxy can stop waiting before the renderer does. A request deadline is not necessarily a process deadline.

Set Grover’s PDF conversion timeout

Grover documents convert_timeout in milliseconds for PDF conversion. Its other timeout options bound different stages: launch_timeout covers starting the browser, while request_timeout covers fetching content and takes precedence over the general timeout for requests.

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.
#1 Best Overall

Here is the configuration shape documented by the project:

Grover.configure do |config|
  config.options = {
    timeout: 0,
    launch_timeout: 3_000,
    request_timeout: 1_000,
    convert_timeout: 30_000
  }
end

These numbers illustrate milliseconds and option placement; they are not a universal production recommendation. In particular, the timeout: 0 example means the general timeout is disabled, not that it expires immediately. Check the README and your installed Grover version before relying on options or defaults.

Choose the option by the stalled stage

  • If the browser process is slow to start, investigate launch_timeout.
  • If the page or its resources take too long to load, investigate request_timeout and resource accessibility.
  • If the page has loaded but creating the PDF takes too long, investigate convert_timeout.
  • Use the general timeout only with an understanding of its scope; do not assume it replaces the stage-specific settings.

For example, a long conversion deadline will not fix a browser that cannot launch, and increasing a request deadline will not repair an asset URL that cannot resolve. Set limits using observed duration for representative documents, including their size and resource-loading behavior, and leave appropriate room within the enclosing job or request deadline.

Set a hard deadline around wkhtmltopdf

Wicked PDF and PDFKit use the external wkhtmltopdf executable. Do not assume the two Ruby wrappers expose one common timeout setting: confirm the behavior of the gem version and the way it starts and waits for its child process.

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

Ruby’s Timeout.timeout accepts seconds, including fractional values, and raises Timeout::Error when its block exceeds the limit. Ruby’s documentation cautions that it “cannot be relied on to enforce timeouts for untrusted blocks.” In particular, wrapping a call that waits for an external renderer does not establish that the renderer was killed, its pipes were closed, or temporary output was cleaned up.

When a hard process deadline matters, own the child-process lifecycle: launch the executable, wait only until a deadline, send a termination signal if it runs too long, allow a short grace period, then force-kill if needed, reap the child, and remove partial output. The following standalone pattern shows those responsibilities for a local HTML file and a wkhtmltopdf executable available on PATH. Adapt arguments to your application and installed renderer; validate it in your environment before using it in production.

require "tmpdir"
require "fileutils"

html_path = ARGV.fetch(0) { abort "usage: ruby render.rb input.html [output.pdf]" }
pdf_path  = ARGV[1] || "output.pdf"
renderer  = ENV.fetch("WKHTMLTOPDF", "wkhtmltopdf")
timeout_seconds = Float(ENV.fetch("PDF_TIMEOUT_SECONDS", "30"))

abort "HTML file not found: #{html_path}" unless File.file?(html_path)
FileUtils.rm_f(pdf_path) # Do not mistake an old PDF for this run's result.

pid = Process.spawn(renderer, html_path, pdf_path, out: File::NULL, err: $stderr)
deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + timeout_seconds
status = nil

begin
  loop do
    waited_pid, status = Process.waitpid2(pid, Process::WNOHANG)
    break if waited_pid
    if Process.clock_gettime(Process::CLOCK_MONOTONIC) >= deadline
      Process.kill("TERM", pid) rescue nil
      grace_deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + 2
      loop do
        waited_pid, status = Process.waitpid2(pid, Process::WNOHANG)
        break if waited_pid
        if Process.clock_gettime(Process::CLOCK_MONOTONIC) >= grace_deadline
          Process.kill("KILL", pid) rescue nil
          _, status = Process.waitpid2(pid) rescue [nil, nil]
          break
        end
        sleep 0.05
      end
      FileUtils.rm_f(pdf_path)
      abort "PDF renderer exceeded #{timeout_seconds} seconds"
    end
    sleep 0.05
  end
rescue Errno::ECHILD
  abort "Renderer process was not available to wait for"
end

unless status&.success? && File.file?(pdf_path) && File.size?(pdf_path)
  FileUtils.rm_f(pdf_path)
  abort "PDF renderer failed; inspect stderr and verify the input and assets"
end

puts "Wrote #{pdf_path}"

This example redirects standard output away and sends standard error to the parent process so diagnostics remain visible. If you capture both streams with pipes instead, drain them while the renderer runs; waiting without reading full pipes can itself block a child. The example handles a direct child process, but if your launcher creates descendants, design and test process-group cleanup for that deployment. Also make sure cleanup does not delete another job’s output: use unique per-job paths when rendering concurrently.

Use the wrapper or manage the process?

  • Use the wrapper’s documented facilities when its behavior matches the required deadline and cleanup guarantees.
  • Manage the child explicitly when you require a firm upper bound on renderer lifetime or need predictable cleanup after timeout.
  • Do not treat either approach as a substitute for safe input handling. Wicked PDF warns that user-generated HTML, CSS, or JavaScript should be sanitized or prevented from requesting internal addresses. For untrusted documents, restrict network access as well as execution time.

Diagnose hangs before raising the limit

Check asset requests and server capacity

A PDF renderer may request stylesheets, images, fonts, or other resources while generating a document. Confirm that those URLs resolve from the renderer’s environment, not merely from your browser. Check stderr and process state, and reproduce with the same HTML, assets, renderer version, and environment.

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

PDFKit documents a development deadlock scenario: a single server process can be blocked waiting for the renderer while the renderer requests assets from that same server. The project points to using multiple server workers or embedding resources to avoid extra requests. Raising the conversion timeout may simply make that deadlock last longer.

Check the surrounding deadlines

Compare renderer limits with the Rails or Rack server, reverse proxy, and job runner. A proxy may give up on the response while a worker keeps rendering. For work that can exceed a normal interactive request window, consider submitting a background job and returning its status or result separately; set the job deadline and renderer deadline deliberately rather than assuming the HTTP timeout controls both.

Separate HTML generation from rendering

Time template and database work independently from browser or executable work. If HTML construction is slow, changing a renderer timeout will not solve the application bottleneck. If rendering is slow, save a representative input and inspect whether the time is spent loading resources or producing the PDF.

Common timeout errors and fixes

Symptom Likely cause What to check or do
Grover times out before conversion begins Browser startup or page/resource requests exceed their own limit. Determine whether launch or requests are slow; tune the matching Grover option and verify the browser and resource URLs.
Grover loads the page but PDF creation exceeds its limit The conversion stage takes longer than convert_timeout. Measure with representative HTML and assets, then set a conversion limit compatible with the enclosing deadline.
Ruby raises Timeout::Error, but wkhtmltopdf remains active The Ruby block timed out, but the child process lifecycle was not explicitly managed. Track the child PID, signal and reap it, and remove incomplete output.
PDFKit appears stuck while requesting local assets A single-process development server may be deadlocked by the renderer’s asset requests. Use multiple server workers or embed resources, and verify requests from the renderer environment.
The browser request fails while PDF work continues A web-server or proxy deadline expired independently of the render job. Separate HTTP and job deadlines; use asynchronous job handling when appropriate.
A previous PDF appears after a failed run Stale output was left at the destination path. Use a unique output path per job or remove the destination before rendering, then publish only a successful non-empty result.
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 the deliverable you need is a screenshot of a web page rather than a PDF generated by your Ruby renderer, ScreenshotNeo provides a website screenshot API. It is not a replacement for Grover, Wicked PDF, or PDFKit when you need their HTML-to-PDF conversion; it is an alternative for capturing a page as an image or PDF without managing a browser in your app.

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

One GET request returns a PNG, JPEG, WebP, or PDF. The API can accept cookies and consent banners as a visitor and remove known consent platforms, newsletter popups, and chat widgets before capture. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and the response includes page-verdict and billing headers. AI agents can use its MCP server tools to take screenshots, get page information, and capture PDFs.

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 and authentication. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000. Sign up for the free plan.

Choose the deadline that matches the work

Use Grover’s stage-specific timeout when the work runs in Grover, and distinguish conversion from browser startup and requests. With wkhtmltopdf wrappers, verify what the gem actually controls; use explicit child-process management when the requirement is a hard renderer deadline. In either case, diagnose resource loading and surrounding service deadlines before increasing limits. No single duration is suitable for every Ruby application.

Frequently Asked Questions

Does Grover’s convert_timeout apply to an image screenshot too?

The documented option described here applies to PDF conversion; confirm the behavior for your installed Grover version and the operation you are running.

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

Can I use Ruby Timeout.timeout as the only safeguard for an untrusted render?

No. Ruby documents that it cannot be relied on to enforce timeouts for untrusted blocks, and it does not establish cleanup of an external renderer process.

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.