How to Fix PuppeteerSharp DownloadAsync Failures
Fix PuppeteerSharp DownloadAsync errors by isolating browser selection, network access, cache permissions, extraction, and launch problems.
Short answer: treat BrowserFetcher.DownloadAsync() as a browser acquisition step, separate from Puppeteer.LaunchAsync(). First record the PuppeteerSharp version, runtime, operating system, architecture, selected browser and exact overload. Then determine whether the failure occurs during build resolution, HTTP transfer, cache writing, archive extraction, or a later missing-executable or permission step.
The most reliable troubleshooting order is:
- Capture the complete exception, including inner exceptions.
- Inspect the selected browser, platform, download host, cache directory and proxy.
- Check revision availability with
CanDownloadAsync(revision). - Verify that the process can reach the archive host and write to the cache.
- After download returns, verify the executable path exists before investigating launch.
- Handle Windows PDF sandbox permissions as a separate deployment issue.
What DownloadAsync actually does
DownloadAsync() downloads a browser revision for PuppeteerSharp. It does not launch Chromium and does not validate that a later page operation, PDF export or screenshot will work. The official repository example awaits the download before calling LaunchAsync(). PuppeteerSharp is a .NET port of the official Node.js Puppeteer API.
There are parameterless, BrowserTag and build-ID overloads. The overload is part of the diagnosis: a dated report from February 15, 2024 described the default download succeeding while an explicit Stable tag returned HTTP 404 on reported PuppeteerSharp 12.0.0 and 14.0.0 projects targeting .NET 8. Treat that report as a version-specific reproduction, not as evidence that every current Stable download fails.
Minimal working example
Start with the default browser selection for the PuppeteerSharp version installed in your project:
using PuppeteerSharp;
var installed = await new BrowserFetcher().DownloadAsync();
await using var browser = await Puppeteer.LaunchAsync(new LaunchOptions
{
Headless = true,
ExecutablePath = installed.GetExecutablePath()
});
await using var page = await browser.NewPageAsync();
await page.GoToAsync("https://example.com");
await page.ScreenshotAsync("example.png");
Pinning a tag or build ID is valid when you have verified that the selected revision exists at the configured host and is compatible with your installed release:
// Tag selection; confirm availability for your PuppeteerSharp version.
var installed = await new BrowserFetcher().DownloadAsync(BrowserTag.Stable);
// Build-ID selection; use the build ID documented for your release.
// var installed = await new BrowserFetcher().DownloadAsync("BUILD_ID");
Do not combine a successful download with an assumed executable location. Use the returned InstalledBrowser and verify the path:
var fetcher = new BrowserFetcher();
var installed = await fetcher.DownloadAsync();
var executable = installed.GetExecutablePath();
Console.WriteLine($"Browser: {fetcher.Browser}");
Console.WriteLine($"Platform: {fetcher.Platform}");
Console.WriteLine($"Base URL: {fetcher.BaseUrl}");
Console.WriteLine($"Cache directory: {fetcher.CacheDir}");
Console.WriteLine($"Executable: {executable}");
Console.WriteLine($"Exists: {File.Exists(executable)}");
Step-by-step diagnosis
1. Record the failure context
Save these values before changing configuration:
- PuppeteerSharp package version and target framework.
- .NET runtime version, operating system and CPU architecture.
- The exact overload and argument: parameterless,
BrowserTagor build ID. - Full exception text and every inner exception.
- Whether the failure occurs on a developer machine, CI runner, container or deployed host.
- The configured
BaseUrl,CacheDirandWebProxy.
2. Identify the failing stage
| Symptom | Likely stage | Next check |
|---|---|---|
DownloadAsync throws HTTP 404 |
Revision or tag resolution | Compare the explicit tag/build ID with the version-appropriate default and inspect BaseUrl. |
| DNS, TLS, timeout or proxy exception | Archive transfer | Test outbound access from the same process identity and review WebProxy. |
| Access denied, disk or path exception | Cache writing | Check CacheDir, permissions, available space and read-only mounts. |
| Archive or extraction error | Transfer integrity or extraction | Check the complete download, temporary storage and antivirus or file-locking rules. |
| Download returns, launch says executable is missing | Post-download path | Call GetExecutablePath and verify the file exists on the deployed host. |
| Launch works, Windows PDF generation hangs | Chromium sandbox permissions | Follow the PDF permission branch below and inspect InstalledBrowser.PermissionsFixed. |
3. Check revision availability
CanDownloadAsync(revision) performs a HEAD request to check whether a revision is available:
var fetcher = new BrowserFetcher();
var revision = "YOUR_REVISION";
var available = await fetcher.CanDownloadAsync(revision);
Console.WriteLine($"Available at {fetcher.BaseUrl}: {available}");
A true result only proves that the HEAD request succeeded. It does not prove that a complete archive can be downloaded, extracted, or executed. A false result points you toward the selected revision and configured host; a true result means you still need to investigate transfer, storage and extraction.
4. Inspect BrowserFetcher controls
The API exposes these settings:
| Property | What to verify |
|---|---|
Browser |
The browser family selected by your installed release and overload. |
Platform |
That the detected platform and architecture match the host. |
BaseUrl |
The download host is reachable and serves the requested revision. |
CacheDir |
The process identity can create directories, write files and read the extracted browser. |
WebProxy |
The proxy permits the archive host and does not rewrite or truncate downloads. |
5. Check network access from the real runtime
A browser download may work on a workstation and fail in CI because the runtime has different DNS, TLS inspection, egress rules or proxy settings. Test from the same container, service account or runner that executes .NET. Capture status codes and inner exceptions, and check whether the proxy requires authentication.
Do not use a successful HEAD request as proof that a GET transfer will work. Firewalls, proxies and content filters can treat the two methods differently.
6. Check cache and extraction permissions
Ensure the cache directory exists or can be created, has enough free space for both the archive and extracted files, and is writable by the deployed identity. A read-only container layer, locked temporary file or endpoint security tool can make an apparently valid download fail during extraction. Configure an explicit cache directory when the default location is unsuitable, and preserve it between runs when your deployment model allows that.
7. Verify the executable before launch
var fetcher = new BrowserFetcher();
var installed = await fetcher.DownloadAsync();
var path = installed.GetExecutablePath();
if (!File.Exists(path))
{
throw new FileNotFoundException(
$"PuppeteerSharp downloaded a browser but the executable is absent: {path}");
}
await using var browser = await Puppeteer.LaunchAsync(new LaunchOptions
{
Headless = true,
ExecutablePath = path
});
If this check fails, stay in the download, cache or extraction branch. A later “path to executable does not exist” launch error is usually a consequence, not a separate browser bug.
Common errors and fixes
“DownloadAsync(BrowserTag) throws a 404 exception”
Cause: the requested tag or revision is not present at the configured host for that PuppeteerSharp release, or the host URL is incorrect.
Fix: print Browser, Platform and BaseUrl; run CanDownloadAsync for the exact revision; compare the explicit tag with the default overload; then choose a documented, available build for your package version.
“Download fails but doesn’t throw an exception”
Cause: the observed symptom may be a swallowed exception, an unobserved task, logging that omits inner exceptions, or a later launch failure misattributed to the download.
Fix: await the call directly, log the complete exception chain, log the returned InstalledBrowser, and check the executable path immediately after the await.
“Path to executable does not exist”
Cause: the archive was not fully downloaded or extracted, the cache is different between build and runtime, or the process cannot read the deployed path.
Fix: call GetExecutablePath on the runtime host, check File.Exists, inspect the cache directory and avoid assuming that a browser downloaded in one image layer is present in another.
Proxy, DNS or TLS errors
Cause: the runtime cannot reach the download host directly, or the configured proxy blocks, authenticates or modifies the archive request.
Fix: configure WebProxy for the process, permit the archive host, validate DNS and certificates from the same runtime identity, and capture the innermost network exception.
Access denied or extraction errors
Cause: an unwritable cache, insufficient disk space, locked temporary files or security software interfering with extraction.
Fix: choose a writable CacheDir, verify free space and file ownership, and inspect endpoint-security logs. Repeating the download without changing those conditions usually reproduces the same failure.
Windows PDF generation permissions
Keep this branch separate from browser acquisition. PuppeteerSharp’s PDF troubleshooting guidance says Chromium 125 introduced sandbox permission requirements for PDF generation. Check InstalledBrowser.PermissionsFixed and, when required by that guidance, run the downloaded setup.exe as administrator. A successful browser download does not by itself prove that PDF sandbox permissions are configured.
Deployment strategies
| Strategy | Advantages | Trade-offs |
|---|---|---|
| Download at application startup | Simple setup and automatic acquisition. | Startup can be slow or fail when production egress is restricted. |
| Download during image build or deployment | Runtime does not wait for acquisition; failures appear earlier. | The browser must be copied into the final image and remain readable there. |
| Pin a build ID | Reproducible browser selection. | The build must remain available at the configured host and match your PuppeteerSharp release. |
| Use the default selection | Lets the installed package choose its expected revision. | Less explicit than a pinned build and still dependent on host availability. |
| Shared or persistent cache | Reduces repeated downloads across runs. | Requires correct permissions, concurrency handling and cache lifecycle management. |
For repeatable server deployment, the official PDF deployment guidance recommends installing the browser before application runtime and passing its path to LaunchAsync. Treat that as a deployment strategy for avoiding runtime installation delay, not as a universal fix for every DownloadAsync exception.
Performance, reliability and cost considerations
- Startup time: runtime downloads add network and extraction work to the first request or process start. Preinstalling moves that work to build or deployment.
- Reliability: pinning a verified build improves reproducibility, while the default overload can avoid selecting a tag that is unavailable for your release.
- Cache design: a persistent cache reduces repeated transfers, but a shared cache must be writable and readable by every worker.
- Network policy: proxy and egress rules are part of the browser installation path. Document them with the deployment.
- Storage: allow room for the archive, temporary files and extracted browser; cleanup policies should not delete the executable between download and launch.
- Cost: PuppeteerSharp itself does not provide a hosted screenshot endpoint; your costs are those of network transfer, storage and compute in your environment.
Or skip the browser setup
If your goal is a website screenshot rather than managing a local Chromium installation, ScreenshotNeo provides a single HTTP request. See the ScreenshotNeo API documentation for all options.
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 removes cookie and consent banners, newsletter popups and chat widgets before capture. Bot checks, blank pages and failed loads are not billed, and response headers report the page verdict and whether the shot was billed. Its MCP server lets Claude, Cursor and other MCP clients call take_screenshot, get_page_info and capture_pdf. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots.
Create a free ScreenshotNeo account and try the API without adding a card.
FAQ
Should I always use BrowserTag.Stable?
No. Use a tag only after confirming that it is available for your PuppeteerSharp version and configured download host. The default overload may resolve differently.
Does CanDownloadAsync guarantee success?
No. It checks revision availability with HEAD. Full transfer, extraction and executable permissions still need separate checks.
Why does launch fail after DownloadAsync succeeds?
The runtime may use a different cache directory, the executable may not have been extracted, or the process may lack permission to read it. Print and verify GetExecutablePath() on the launch host.
Is a proxy setting required for every deployment?
No. It is required only when the runtime reaches the browser archive through a proxy or when direct egress is blocked. Configure WebProxy to match the actual network path.
Can ScreenshotNeo replace PuppeteerSharp for every browser task?
No. ScreenshotNeo is a hosted screenshot and PDF API. PuppeteerSharp remains useful when your application needs local browser automation, custom page interactions or control over the browser process.


