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

iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more

Puppeteer saves a page download wherever you set its browser downloadPath. On Heroku, that path is on the filesystem of the dyno running Chrome. The file is temporary, isolated from other dynos, and deleted when that dyno stops or restarts. Serve it immediately from that same dyno for a transient response; upload it to managed object storage such as Amazon S3 when it must survive restarts or be available to every dyno.

The direct answer

There is no Heroku-wide Puppeteer download folder. Your code chooses the destination through Puppeteer’s download behavior. The path can be an absolute directory created by your application, such as a subdirectory under the operating system’s temporary directory.

Three paths are often confused:

  • Download path: the directory that receives files fetched by a web page after downloads are allowed.
  • Puppeteer browser cache: Puppeteer’s default cache (from version 19.0.0, typically ~/.cache/puppeteer) stores Chromium or Firefox binaries. It is not where page downloads are saved.
  • Heroku dyno filesystem: the local filesystem belonging only to the dyno that ran the browser. Runtime-written files are ephemeral and disappear when that dyno is replaced or restarted.

The Puppeteer API requires a path when the download policy is allow or allowAndName. If you do not configure an allowed download behavior and destination, a click may open a browser download prompt or fail to produce a usable file in headless mode.

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

How Heroku changes file access

Every dyno has its own filesystem

Heroku’s Dyno Isolation documentation (updated December 3, 2024) describes each dyno as having its own ephemeral filesystem with a fresh copy of the deployed code. A web dyno, worker dyno, and one-off dyno do not see one another’s runtime-created files. A request routed to a different web dyno therefore cannot read a download created by the first dyno.

#1 Best Overall
Sale
Seagate 2TB Portable Hard Drive | USB 3.0 (STGX2000400)
  • Easily store and access 2TB to content on the go with the Seagate Portable Drive, a USB external hard drive
  • Designed to work with Windows or Mac computers, this external hard drive makes backup a snap just drag and drop
  • To get set up, connect the portable hard drive to a computer for automatic recognition no software required
  • This USB drive provides plug and play simplicity with the included 18 inch USB 3.0 cable
  • The available storage capacity may vary.

Restarts delete runtime files

Files written while the app runs are discarded when that dyno stops, including automatic restarts and dyno replacement. A path that exists now is not a durable storage location, even if it is inside your application directory.

A one-off shell is not your web dyno

heroku run bash starts a separate one-off dyno. It is useful for inspecting the slug and testing commands, but it will not contain a file that a running web dyno downloaded. For Cedar-generation apps, Heroku Exec can connect to an existing dyno and supports copying files out. Fir-generation apps use heroku run:inside for tasks inside an existing dyno. The command available depends on the app’s runtime generation.

Configure a reliable temporary download in Puppeteer

The following Node.js example creates a unique temporary directory, permits downloads, clicks a link, waits for the file to finish, and streams the result from the same process. Install Puppeteer with npm install puppeteer. Heroku deployments commonly need the --no-sandbox flags shown below unless your buildpack and runtime provide a configured sandbox.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
const puppeteer = require('puppeteer');
const fs = require('fs/promises');
const path = require('path');
const os = require('os');

async function waitForDownload(dir, timeoutMs = 60000) {
  const started = Date.now();
  let previous = new Set();

  while (Date.now() - started < timeoutMs) {
    const names = await fs.readdir(dir);
    const complete = names.filter(name => !name.endsWith('.crdownload'));
    if (complete.length) {
      const candidate = complete[0];
      const full = path.join(dir, candidate);
      const first = (await fs.stat(full)).size;
      await new Promise(resolve => setTimeout(resolve, 500));
      const second = (await fs.stat(full)).size;
      if (first === second && second > 0) return full;
    }
    previous = new Set(names);
    await new Promise(resolve => setTimeout(resolve, 250));
  }
  throw new Error('Timed out waiting for the browser download');
}

(async () => {
  const downloadPath = await fs.mkdtemp(path.join(os.tmpdir(), 'puppeteer-'));
  const browser = await puppeteer.launch({
    headless: true,
    args: ['--no-sandbox', '--disable-setuid-sandbox']
  });

  try {
    const context = browser.defaultBrowserContext();
    await context.setDownloadBehavior({
      behavior: 'allow',
      downloadPath
    });

    const page = await browser.newPage();
    await page.goto('https://example.com/reports', {
      waitUntil: 'networkidle2',
      timeout: 60000
    });
    await page.click('#download-report');

    const filePath = await waitForDownload(downloadPath);
    console.log(`Downloaded ${filePath}`);

    // Use filePath immediately: upload it or send it in your HTTP response.
  } finally {
    await browser.close();
    await fs.rm(downloadPath, { recursive: true, force: true });
  }
})();

Replace the URL and selector with the page you control. A unique directory prevents concurrent requests from mistaking one job’s filename for another. The polling loop ignores Chrome’s temporary .crdownload file and checks that the final file size has stopped changing.

Rank #2
Seagate Portable 5TB External Hard Drive HDD – USB 3.0 for PC, Mac, PS4, & Xbox - 1-Year Rescue Service (STGX5000400), Black
  • Easily store and access 5TB of content on the go with the Seagate portable drive, a USB external hard Drive
  • Designed to work with Windows or Mac computers, this external hard drive makes backup a snap just drag and drop
  • To get set up, connect the portable hard drive to a computer for automatic recognition software required
  • This USB drive provides plug and play simplicity with the included 18 inch USB 3.0 cable
  • The available storage capacity may vary.

Use allowAndName when you need deterministic names

Puppeteer’s download behavior also supports allowAndName. It still requires downloadPath, but the browser chooses a generated name based on the download. If your application needs a business-facing filename, rename the completed file after the download and before uploading or responding. Do not assume the URL’s last path segment is the final filename; servers can set a Content-Disposition header or redirect to another resource.

Make the download endpoint observable

Log the dyno identifier, job identifier, configured directory, resulting filename, byte size, and upload status. These values let you determine whether a later request reached a different dyno and whether the failure occurred during navigation, download, or persistence. Never log cookies, Authorization headers, or the downloaded document itself.

Return the file while it still exists

Synchronous response

If the download completes within one web request, set the response’s content type and disposition, stream the file, and remove the temporary directory after the stream closes. This works only while the producing dyno remains alive. A subsequent request may be routed elsewhere, so do not return a local path to a client and expect that path to work later.

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

Asynchronous job

For longer downloads, enqueue a job, let a worker run Puppeteer, and report a job status from your application. Once the worker has the complete file, upload it to managed object storage and save the object key with the job record. The client can then receive an application-controlled URL or a streamed response generated from that object.

Rank #3
Seagate Portable 1TB External Hard Drive HDD – USB 3.0 for PC, Mac, PlayStation, & Xbox, 1-Year Rescue Service (STGX1000400) , Black
  • Easily store and access 1TB to content on the go with the Seagate Portable Drive, a USB external hard drive.Specific uses: Personal
  • Designed to work with Windows or Mac computers, this external hard drive makes backup a snap just drag and drop. Reformatting may be required for Mac
  • To get set up, connect the portable hard drive to a computer for automatic recognition no software required
  • This USB drive provides plug and play simplicity with the included 18 inch USB 3.0 cable
  • The available storage capacity may vary.

Persist downloads with managed object storage

Heroku recommends a managed object store such as Amazon S3 for application files that must survive dyno replacement or restart. The durable workflow is:

  1. Create a dyno-local temporary directory and configure Puppeteer’s downloadPath to it.
  2. Download and validate the completed file (size, expected media type, and any application-specific checksum).
  3. Upload the bytes to your object-storage bucket using credentials supplied through Heroku config vars, not hard-coded in source.
  4. Store the object key and metadata in your database or job record.
  5. Return an application URL, a time-limited signed object URL, or stream the object through your application.
  6. Delete the dyno-local copy in a finally block after a successful upload; also clean up abandoned job directories.
Approach Lifetime Cross-dyno access Best use
Dyno-local directory Until the dyno stops or restarts Only the dyno that created it Immediate, temporary response
Managed object storage According to the bucket’s retention and lifecycle rules Available to dynos through the storage service Downloads needed later or by multiple dynos

The reviewed Heroku guidance does not establish a provider price or retention period; configure those according to your storage service and application policy.

Common failures and fixes

“The file is not in ~/.cache/puppeteer”

That directory is the browser binary cache, not the page-download destination. Inspect the value passed as downloadPath and log it at runtime.

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.

“Download behavior requires a path”

Set an absolute, writable directory and pass it with behavior: 'allow' or 'allowAndName'. Create the directory before configuring the browser.

Rank #4
Seagate Portable 4TB External Hard Drive HDD – USB 3.0, 1-Year Rescue
  • Easily store and access 4TB of content on the go with the Seagate Portable Drive, a USB external hard drive.Specific uses: Personal
  • Designed to work with Windows or Mac computers, this external hard drive makes backup a snap just drag and drop
  • To get set up, connect the portable hard drive to a computer for automatic recognition no software required
  • This USB drive provides plug and play simplicity with the included 18 inch USB 3.0 cable
  • The available storage capacity may vary.

“It works locally but not on Heroku”

Check browser launch dependencies and sandbox flags, then verify that the download click actually starts a download rather than opening a new tab, returning an authorization error, or triggering a bot check. Capture the page URL, response status, and console errors without recording secrets.

“The next request cannot find the file”

It probably reached another dyno, or the original dyno restarted. Serve the response in the same request or upload the result to object storage before returning a durable reference.

“heroku run bash shows an empty directory”

That shell is a new one-off dyno. Use Heroku Exec on Cedar or heroku run:inside on Fir when you must inspect the dyno that actually created the file, subject to the app’s runtime and feature availability.

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.

“The response is sent before the file is complete”

Wait for the temporary download suffix to disappear and for the file size to remain stable, or use a Puppeteer download event where supported. Then close the browser only after the file has been handed to your upload or response stream.

Best Value
Sale
UnionSine 500GB Ultra Slim Portable External Hard Drive HDD-USB 3.0
  • [Upgraded Version] - This external hard drive features a mirrored logo stripe combined with a striped anti-slip design, and the rounded corners of the casing make it easier to grip. The stripes also have a heat dissipation function, ensuring stable and fast data transfer.
  • 【Ultra-thin and quiet】 - The motherboard adopts JMicron 578 noise-free solution, giving you a quiet working environment. Lightweight and portable size designed to fit in your pocket for easy portability.
  • 【Ultra-Fast Data Transfers】 - Pairing this external hard drive with JMicron 578 solution USB 3.0 and USB 2.0 interfaces enables blazing-fast data transfer. It boasts theoretical read speeds of up to 125MB/s and write speeds of up to 103MB/s.
  • 【Plug and Play】 - With no software to install, just plug it in and the drive is ready to use.The hard disk chip is wrapped with an aluminum anti-interference layer to increase heat dissipation and protect data.
  • 【What You Get】 - 1 x Portable Hard Drive, 1 x USB 3.0 Cable, 1 x User Manual, Gift-type shell packaging ,Three-year manufacturer's warranty and free technical support services.

“Concurrent jobs return the wrong file”

Use one randomly created directory per job and retain the job’s own file path. A shared directory makes two simultaneous downloads indistinguishable when both pages choose similar filenames.

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

Performance, reliability, and security considerations

  • Keep browser lifetimes bounded: set navigation and download timeouts and always close the browser in a finally block.
  • Limit concurrency: each Chromium process consumes memory and temporary disk; queue work rather than starting an unbounded number of browsers on one dyno.
  • Use network-idle waits carefully: pages with analytics or streaming connections may never become idle. Prefer a specific selector or download trigger plus a timeout.
  • Validate before storage: check size and type, and reject unexpectedly large files before uploading.
  • Protect downloaded data: treat files and object URLs as sensitive, use private buckets by default, and avoid exposing raw dyno paths.
  • Design for retries: make uploads idempotent with a job-specific object key, and record completion only after the object store confirms success.

Or skip the browser setup

If your actual goal is a website screenshot rather than downloading a file exposed by a page, ScreenshotNeo provides a one-request screenshot API and an MCP server for Claude, Cursor, and other MCP clients. It accepts cookie or consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing status.

cURL (the API documentation is at https://screenshotneo.com/docs/):

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
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}`);

ScreenshotNeo also supports full-page and element captures, device and retina settings, PDFs, custom CSS and JavaScript, waits, request blocking, headers and cookies, geolocation, caching, signed links, asynchronous webhooks, and bulk capture. The free plan includes 1,000 screenshots each month with no card; paid plans start at $5 for 3,000 screenshots. Sign up for the free plan.

Frequently Asked Questions

How can I prevent two download jobs from colliding on the same filename?

Create a separate randomly named download directory for every job, retain that directory in the job record, and only move the completed file to shared storage after the download has stabilized.

What should an API return while a worker is still downloading?

Return a job identifier and a status URL, then expose the finished object only after the worker confirms a successful upload. This avoids promising a dyno-local path that may disappear or belong to another dyno.

Quick Recap

SaleBestseller No. 1
Seagate 2TB Portable Hard Drive | USB 3.0 (STGX2000400)
Seagate 2TB Portable Hard Drive | USB 3.0 (STGX2000400)
This USB drive provides plug and play simplicity with the included 18 inch USB 3.0 cable; The available storage capacity may vary.
$129.99
Bestseller No. 2
Seagate Portable 5TB External Hard Drive HDD – USB 3.0 for PC, Mac, PS4, & Xbox - 1-Year Rescue Service (STGX5000400), Black
Seagate Portable 5TB External Hard Drive HDD – USB 3.0 for PC, Mac, PS4, & Xbox - 1-Year Rescue Service (STGX5000400), Black
This USB drive provides plug and play simplicity with the included 18 inch USB 3.0 cable; The available storage capacity may vary.
$229.99
Bestseller No. 3
Seagate Portable 1TB External Hard Drive HDD – USB 3.0 for PC, Mac, PlayStation, & Xbox, 1-Year Rescue Service (STGX1000400) , Black
Seagate Portable 1TB External Hard Drive HDD – USB 3.0 for PC, Mac, PlayStation, & Xbox, 1-Year Rescue Service (STGX1000400) , Black
This USB drive provides plug and play simplicity with the included 18 inch USB 3.0 cable; The available storage capacity may vary.
$119.80
Bestseller No. 4
Seagate Portable 4TB External Hard Drive HDD – USB 3.0, 1-Year Rescue
Seagate Portable 4TB External Hard Drive HDD – USB 3.0, 1-Year Rescue
This USB drive provides plug and play simplicity with the included 18 inch USB 3.0 cable; The available storage capacity may vary.
$208.99

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.

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