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

OSError: [Errno 24] Too many open files means the Python process has run out of file descriptors. With Pyppeteer, first make sure every page and browser is closed on success, failure, timeout, and cancellation; reuse a browser where practical; and run work through one managed asyncio event loop. Measure descriptor use before raising the service’s open-file limit: a higher limit can accommodate legitimate load, but it will only delay failure if descriptors are leaking.

What “Too Many Open Files” means

On Unix-like systems, processes use file descriptors to access files, pipes, sockets, and other resources. When a process reaches its available descriptor limit, an operation that needs another descriptor can fail with OSError: [Errno 24] Too many open files. In a Pyppeteer service, the count can include resources held by Python, the browser connection, pages, and subprocess pipes. It is not necessarily a sign that your code opened too many ordinary files.

A reported Pyppeteer incident found a new FIFO pipe associated with the Python process on each request while the application launched a browser for every request. The code closed the browser only along its success path and created a new event loop for each request. The author later reported that calling browser.process.communicate() closed the open pipes. That is useful incident evidence, not a guarantee about every Pyppeteer version or an instruction to call communicate() on every request.

Fix resource ownership before raising limits

Make it clear which part of the program owns each browser and page, and ensure that owner closes it. Pyppeteer documents Browser.close() as closing connections and terminating the browser process. A Page represents an individual tab and can be closed independently. Put cleanup in finally blocks so an exception, navigation timeout, or cancellation does not skip it.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
#1 Best Overall
Apple 2026 MacBook Neo 13-inch Laptop with A18 Pro chip: Built for AI and Apple Intelligence, Liquid Retina Display, 8GB Unified Memory, 256GB SSD Storage, 1080p FaceTime HD Camera; Blush
  • AN AMAZING MAC AT A SURPRISING PRICE — With an incredibly portable and durable aluminum design, up to 16 hours of battery life,* and the A18 Pro chip, MacBook Neo is ready to go wherever school takes you.
  • FOUR STUNNING COLORS. ONE DURABLE DESIGN — Choose from four beautiful colors — Silver, Blush, Citrus, or Indigo — each with a color-coordinated keyboard. And MacBook Neo is made with a durable recycled aluminum enclosure that helps it reach 60 percent recycled content by weight — the most ever in any Apple product.*
  • FLY THROUGH EVERYDAY ASSIGNMENTS — Whether you’re cramming for finals, using Apple Intelligence* to summarize class notes, creating presentations, or even playing the latest Apple Arcade game,* MacBook Neo delivers the performance and AI capabilities you need to get things done.
  • UP TO 16 HOURS OF BATTERY LIFE — MacBook Neo delivers all day battery life, so you can power through from early morning classes to late night study sessions without worrying about plugging in.
  • A VIBRANT 13-INCH DISPLAY* — The gorgeous Liquid Retina display on MacBook Neo supports 1 billion colors, so photos and videos pop and text is crisp for easy reading.

Reuse a browser and close each page

For a batch or worker, launch a browser once, create a page for each job, and close that page when the job finishes. Close the shared browser when the batch or worker is shutting down, not after every URL. The following is an illustrative pattern, not a claim of having been executed or tested. It uses a fixed list so it can be run as a basic script after Pyppeteer is installed.

import asyncio
from pyppeteer import launch

URLS = [
    "https://example.com/",
    "https://stripe.com/",
]

async def fetch(browser, url):
    page = await browser.newPage()
    try:
        await page.goto(url, {"timeout": 50_000, "waitUntil": "load"})
        return await page.content()
    finally:
        await page.close()

async def main(urls, parallel=4):
    browser = await launch(
        headless=True,
        handleSIGINT=True,
        handleSIGTERM=True,
        handleSIGHUP=True,
    )
    gate = asyncio.Semaphore(parallel)

    async def one(url):
        async with gate:
            return await fetch(browser, url)

    try:
        return await asyncio.gather(
            *(one(url) for url in urls),
            return_exceptions=True,
        )
    finally:
        await browser.close()

if __name__ == "__main__":
    results = asyncio.run(main(URLS, parallel=4))
    for url, result in zip(URLS, results):
        if isinstance(result, BaseException):
            print(f"FAILED {url}: {result!r}")
        else:
            print(f"OK {url}: {len(result)} characters")

The semaphore limits the number of jobs using pages at once; set it according to measurements of your workload and descriptor use, not a supposed universal pages-per-browser rule. return_exceptions=True lets this batch collect individual failures without abandoning the other results. Each page still closes in finally, and the browser closes after the batch even if the batch itself raises or is cancelled.

Make cleanup resilient to failures

  • Close a page in the same function that creates or takes ownership of it. Do not rely on a later request to clean up a prior tab.
  • Close the browser in the worker or batch shutdown path. If a browser is shared among jobs, do not let one job terminate it while other jobs are using it.
  • Exercise timeout, navigation-error, and cancellation paths; successful requests alone do not demonstrate that cleanup is complete.
  • If page cleanup itself raises during shutdown, record that failure and investigate it rather than silently removing cleanup. Keep the browser-level finally so a page-level problem does not make the browser owner disappear.

Use one managed event loop

Do not create a fresh event loop for each URL as a substitute for resource cleanup. A loop is a resource owner too, and repeatedly constructing loops complicates shutdown and makes it harder to keep browser work within a predictable lifetime.

For a normal script, call asyncio.run() once at the top level, as in the example. Python’s asyncio.run() runs the awaitable, finalizes asynchronous generators, shuts down the default executor, and closes the loop. If an application needs several top-level async calls under one managed loop, asyncio.Runner provides a way to manage that lifecycle. In environments that already run an event loop, use that environment’s async entry point rather than trying to call asyncio.run() inside the running loop.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Rank #2
Sale
Apple 2026 MacBook Air 13-inch Laptop with M5 chip: Built for AI, 13.6-inch Liquid Retina Display, 16GB Unified Memory, 512GB SSD, 12MP Center Stage Camera, Touch ID, Wi-Fi 7; Midnight
  • BUILT FOR COLLEGE. AND BEYOND — MacBook Air with the M5 chip packs blazing speed and powerful AI capabilities into an incredibly portable design. And with up to 18 hours of battery life,* this thin and light powerhouse is ready to take on almost any major, just about anywhere.
  • TEAR THROUGH TOUGH ASSIGNMENTS — With its faster CPU and unified memory, the M5 chip delivers even more performance and fluidity across apps, making multitasking and creative workflows smooth and responsive. A powerful Neural Engine and next-generation GPU with Neural Accelerators give you a powerful platform for AI.
  • MAKE QUICK WORK OF YOUR TO-DO LIST — Apple Intelligence helps you write, express yourself, and get things done effortlessly — whether it’s for school or everyday life. With groundbreaking privacy protections, it gives you peace of mind that no one else can access your data — not even Apple.*
  • UP TO 18 HOURS OF BATTERY LIFE — MacBook Air delivers incredible battery life with amazing performance, so you can power through a full day of classes without worrying about plugging in.
  • A BRILLIANT 13.6-INCH DISPLAY* — The gorgeous Liquid Retina display on MacBook Air supports 1 billion colors, making photos and videos pop with rich contrast and sharp detail, and text appears supercrisp. So everything — from class presentations to movies to games — looks truly stunning.

If manual loop management is unavoidable, make sure every exit path shuts down asynchronous generators and the default executor, then closes the loop. Those steps manage loop resources; they do not replace closing Pyppeteer pages, the browser, or its subprocess.

Should you call browser.process.communicate()?

Not as a blind extra cleanup call on every request. Python’s asyncio subprocess communicate() closes the subprocess standard input, reads standard output and standard error to EOF, and waits for process termination. This can matter when a subprocess has piped streams. By contrast, wait() can deadlock if streams are configured as pipes and the output fills an operating-system pipe buffer.

The Pyppeteer incident described above specifically reported browser.process.communicate() as the step that closed its pipes. Treat that as a clue to inspect the subprocess lifecycle in the Pyppeteer version and launch configuration you actually use—not as a universal promise that calling it will fix a leak. Pyppeteer’s documented Browser.close() already closes connections and terminates the browser process. Do not call communicate() while expecting a still-running process to finish on its own; it waits for termination. First establish who owns and terminates the process, whether the streams are piped, and whether the process has exited. Avoid layering subprocess calls onto normal cleanup without confirming their behavior for your version.

Bound concurrency and measure descriptors

Unbounded simultaneous pages and navigations can use descriptors faster than a process can handle them. A semaphore, as in the example, or a fixed-size worker queue makes that load explicit. There is no established universal number of descriptors per Pyppeteer browser or page, so do not choose a concurrency limit from a rule of thumb alone.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Rank #3
HP OmniBook 3 17.3 inch Laptop PC, FHD Display, AMD Ryzen 3 30, 8 GB RAM, 512 GB SSD, AMD Radeon 610M Graphics, Windows 11 Home, Mica Silver, 17-dp0199nr
  • FULL HD IPS DISPLAY - Enjoy vibrant, crystal-clear images with 178-degree wide-viewing angles
  • AMD RYZEN 3 30 PROCESSOR - Everyday performance you can count on; Multitask, stream, game casually, and edit photos smoothly with responsive power and vibrant HDR visuals
  • ENJOY UP TO 14 HOURS AND 15 MINUTES OF BATTERY LIFE - HP Fast Charge restores battery from 0 to 50% in approximately 45 minutes
  • AMD RADEON 610M GRAPHICS - Experience smooth entertainment; Built for streaming and multitasking, enjoy realistic visuals and efficient performance for work and play
  • STORAGE AND MEMORY - 512 GB PCIe NVMe M.2 SSD offers fast speed and efficient storage; and 8 GB LPDDR5 RAM memory boosts performance with higher bandwidth

Count descriptors from inside the Python process

On Linux, inspect /proc/self/fd from the process whose usage you want to know. Calling a shell command such as ls /proc/self/fd counts the shell’s descriptors, not necessarily those of your Python service. A simple in-process diagnostic is:

import os

def descriptor_count():
    try:
        return len(os.listdir("/proc/self/fd"))
    except OSError as exc:
        return f"unavailable: {exc}"

Log the count at a consistent point—such as before a batch, after it finishes, and periodically under steady traffic. The directory listing is a snapshot, not a perfect accounting tool, but its trend can help distinguish accumulation from a stable workload. Use an equivalent process-level descriptor metric on systems that do not expose Linux’s /proc interface.

Interpret the trend, not just one reading

  • If the count rises request after request during steady traffic and does not move back toward baseline after work completes, look for a lifecycle leak before increasing capacity.
  • If the count is stable but close to the process ceiling under expected concurrency, the workload may need a higher descriptor budget or a lower concurrency bound.
  • If failures appear only at peak load, compare descriptor counts with concurrent pages, requests, and browser processes. Reduce the bound temporarily to see whether usage stabilizes.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

When and how to raise the open-file limit

Raise the limit only after you have checked cleanup and measured normal usage. Increasing it can be appropriate when a service’s stable, legitimate concurrency approaches its current limit. If the descriptor count continues to grow, a larger limit only postpones the next failure.

The effective limit belongs to the process that runs the service. A shell’s ulimit value may not match a process started by a service manager, supervisor, or container. Check both the soft limit used by the process and its hard limit, then configure the environment that actually launches Python. Depending on deployment, relevant settings can include ulimit, /etc/security/limits.conf, or supervisord’s minfds. Restart the service after changing its configuration and verify the effective limit from the running process.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Rank #4
Dell 15.6 Laptop, FHD, Intel Core 3 100U, 8 GB RAM, Windows 11 Home
  • Effortlessly chic. Always efficient. Finish your to-do list in no time with the Dell 15, built for everyday computing with Intel Core 3 processor.
  • Designed for easy learning: Energy-efficient batteries and Express Charge support extend your focus and productivity.
  • Stay connected to what you love: Spend more screen time on the things you enjoy with Dell ComfortView software that helps reduce harmful blue light emissions to keep your eyes comfortable over extended viewing times.
  • Type with ease: Write and calculate quickly with roomy keypads, separate numeric keypad and calculator hotkey.
  • Ergonomic support: Keep your wrists comfortable with lifted hinges that provide an ergonomic typing angle.

Tornado’s deployment documentation notes that increasing the number of open files per process may be necessary to avoid this error and gives a value of 50,000 as an illustrative configuration example. That figure is not a measured Pyppeteer recommendation or a universal safe limit. Choose a service-level value based on observed descriptor use, the deployment’s hard ceiling, and the expected workload.

Verify the fix under failure conditions

  1. Record the process’s descriptor count and effective soft and hard open-file limits before a run.
  2. Run a bounded batch using one top-level event loop, page cleanup in finally, and one browser owner.
  3. Confirm that browser processes exit when their owner shuts down and that the descriptor count moves back toward its baseline after the batch.
  4. Repeat with a navigation timeout, a failing URL, and cancellation. Confirm cleanup still runs on each path.
  5. If descriptor use grows per request, identify the unclosed owner before changing limits. If use is stable but too close to the ceiling, adjust the concurrency bound or service-level limit, then measure again.

Or skip the browser setup

If your job is to produce website screenshots or PDFs rather than automate a browser for broader page interaction, ScreenshotNeo offers a screenshot API and MCP server. A single GET request can return a PNG, JPEG, WebP, or PDF. Its capture flow accepts cookie or consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets; those steps can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and responses identify the page verdict and billing status in headers. Its MCP server includes take_screenshot, get_page_info, and capture_pdf for AI agents and MCP clients.

For example, this cURL request captures Stripe as a WebP image. See the ScreenshotNeo API documentation for request options.

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

ScreenshotNeo is for obtaining a screenshot or PDF; it is not a fix for leaked descriptors in an existing Pyppeteer service. Its free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000, and yearly billing gives two months free. Every feature is available on every plan. Learn about ScreenshotNeo or sign up for 1,000 free screenshots a month with no card.

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

Frequently Asked Questions

What is the FIFO mentioned in the Pyppeteer incident?

A FIFO is a named pipe, a form of inter-process communication. In the reported incident, the author observed a new FIFO pipe on the Python process for each request while launching a browser each time.

Does Errno 24 mean the browser alone is leaking descriptors?

No. It means the process has exhausted descriptors, which can be held by pipes, sockets, files, browser connections, or other resources. A process-level descriptor trend helps narrow down whether usage accumulates or is simply high under concurrency.

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.