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

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

Start by proving what failed. An “Access Denied” page is usually an HTTP response generated by the target application, a WAF/CDN, an authentication gateway, a corporate proxy, or an egress policy—not proof that Chrome failed to start. First record the browser and driver versions, final URL, redirects, page source, cookies, headers and network identity. Then run the same account, URL, Chrome build, proxy, locale, viewport and timing in headed and unified headless Chrome. Only after that evidence is collected should you change an option.

What “Access Denied” actually tells you

There are two different failures that look similar in logs:

  • Browser startup failure: Selenium raises an exception such as SessionNotCreatedException, cannot find the Chrome binary, or cannot create a session.
  • Denial document: Chrome starts, navigation completes, and the page contains a 401, 403, challenge, login redirect, rate-limit message, or gateway banner.

Keep these cases separate. A denial document can be produced by the site itself, a CDN or WAF, an identity provider, a company proxy, DNS policy, TLS interception, or the CI runner’s outbound IP. The page title and body are evidence about the response layer; they are not a diagnosis of a missing Chrome flag.

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

Build a minimal, current Selenium session

Use Selenium 4’s Chrome options API. The old options.headless = True property was removed; use the argument shown below. Chrome’s current implementation uses a unified headless and headful code path. Since Chrome 132, the old headless implementation exists only as the separate chrome-headless-shell binary.

from pathlib import Path
import json
import platform
import time

from selenium import webdriver
from selenium.common.exceptions import WebDriverException

TARGET = "https://example.com/"
HEADLESS = True
ARTIFACTS = Path("selenium-artifacts")
ARTIFACTS.mkdir(exist_ok=True)

options = webdriver.ChromeOptions()
if HEADLESS:
    options.add_argument("--headless=new")
options.add_argument("--window-size=1365,900")
# Keep logging available for diagnostics; it does not make a request successful.
options.set_capability("goog:loggingPrefs", {"browser": "ALL", "performance": "ALL"})

try:
    driver = webdriver.Chrome(options=options)
except WebDriverException as exc:
    print("Chrome session did not start:", repr(exc))
    raise

try:
    driver.get(TARGET)
    time.sleep(2)

    diagnostics = {
        "platform": platform.platform(),
        "capabilities": driver.capabilities,
        "current_url": driver.current_url,
        "title": driver.title,
        "user_agent": driver.execute_script("return navigator.userAgent"),
        "language": driver.execute_script("return navigator.language"),
        "languages": driver.execute_script("return navigator.languages"),
        "viewport": driver.execute_script("return {width: innerWidth, height: innerHeight, dpr: devicePixelRatio}"),
        "cookies": driver.get_cookies(),
    }
    (ARTIFACTS / "diagnostics.json").write_text(json.dumps(diagnostics, indent=2, default=str))
    (ARTIFACTS / "page.html").write_text(driver.page_source, encoding="utf-8")
    driver.save_screenshot(str(ARTIFACTS / "page.png"))
    (ARTIFACTS / "browser.log").write_text(json.dumps(driver.get_log("browser"), indent=2), encoding="utf-8")
    print(json.dumps(diagnostics, indent=2, default=str))
finally:
    driver.quit()

The script records the final URL rather than assuming it is the requested URL, because a denial frequently follows one or more redirects. It also saves the response-rendered DOM, cookies, console messages and a screenshot. Selenium navigation by itself does not guarantee a direct HTTP status API; obtain status, response headers and the full redirect chain with a network capture layer when those fields matter.

Check versions and options before investigating the site

Match Chrome and ChromeDriver

ChromeDriver and Chrome browser major versions should match. Print both versions from driver.capabilities and the installed browser. Selenium Manager can resolve a missing driver automatically, but pinned browser and driver packages are easier to reproduce in CI. A mismatch normally causes session creation errors, although it is still worth eliminating before interpreting a page returned by the site.

Use deterministic dimensions

Add a fixed --window-size when responsive layouts, consent dialogs or policy rules depend on viewport dimensions. Record device-pixel ratio as well. Do not add a long list of “stealth” switches at this stage: each one changes an input you then have to explain.

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

Confirm the binary and environment

In containers and remote nodes, verify that Chrome is installed, sandbox requirements are satisfied, and the process can reach the target DNS name. A local headed success says nothing about a CI runner’s proxy, TLS interception, DNS resolver or egress IP.

Capture the denial, not just the exception

Open selenium-artifacts/page.html and inspect the first visible text and HTML comments. Search for provider names, challenge scripts, login endpoints, “rate limit,” “request blocked,” or a corporate gateway banner. Preserve:

  • Requested URL, final URL and every redirect location.
  • Rendered body, page title, screenshot and browser console output.
  • Cookies and whether an authenticated session was present.
  • User-Agent, client hints, language, timezone, viewport and device-pixel ratio.
  • Proxy settings, DNS result, TLS interception indicators and outbound IP.
  • Navigation start time, response timing and whether the page timed out.

For status codes and headers, use Chrome performance logging or an external proxy/network capture appropriate to your environment. Treat those records as sensitive: cookies, Authorization headers and screenshots can contain credentials or personal data.

Compare headed and headless runs scientifically

Run the same script twice, changing only whether --headless=new is present. Use the same account, URL, Chrome build, driver, proxy, locale, timezone, viewport, wait strategy and host. A useful comparison table is:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Observation What it suggests Next check
Both modes receive the same denial Likely account, IP, rate limit, WAF policy, authentication or network control Inspect status, headers, redirects, gateway identity and allowlist rules
Only headless is denied A detectable environment difference is plausible Compare headers, client hints, JavaScript properties, viewport, language, timezone, WebGL/GPU and startup timing
Only the remote node is denied Egress IP, proxy, DNS or TLS policy differs Repeat from the same network identity or obtain the node’s network logs
Session creation fails before navigation Local Selenium, binary or version problem Fix installation and major-version compatibility first

A 2026 arXiv study found that header-level signals accounted for 75% of Chromium-headless-only blocks in its experiment. That figure is not a universal success rate or a promise about a particular WAF, but it is a reason to capture User-Agent and client-hint differences early rather than beginning with random flags.

Inspect headers and browser-visible signals

Record the browser’s User-Agent and language as shown in the script. If you have a permitted network capture, compare request headers and client hints between the two modes. In page JavaScript, compare:

  • navigator.userAgent, navigator.language and navigator.languages.
  • innerWidth, innerHeight and devicePixelRatio.
  • Timezone, WebGL renderer and GPU-related behavior.
  • Whether consent or login cookies exist before the protected navigation.
  • Elapsed time between startup, first navigation and subsequent requests.

Do not assume that changing one value will make access legitimate or reliable. If a provider intentionally blocks automation, the durable choices are a documented allowlist, an official API, or a supported authentication flow.

Check identity, proxy and policy controls

Authentication and session state

Follow the site’s supported login flow, then preserve the resulting cookies or profile only as its terms allow. A redirect to an identity gateway can look like a WAF denial if you inspect only the final screen. Check whether the headed run used a different account, cached profile or prior consent.

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

Proxy, DNS and TLS

Compare proxy environment variables and Selenium proxy capabilities on every machine. Resolve the hostname from both locations, check the outbound public IP, and determine whether a corporate proxy or TLS inspection device inserts its own certificate or banner. Remote Selenium nodes can have completely different network identity and restrictions from your workstation.

Rate limits and egress reputation

Slow repeated retries can turn a diagnostic into a rate-limit event. Record request timing, stop retrying when the provider signals a limit, and ask the operator for an allowlist when your automation is authorized.

Common errors and targeted fixes

Symptom Likely cause Fix
SessionNotCreatedException mentioning version Chrome and ChromeDriver major versions differ Install matching majors or let Selenium Manager resolve a compatible driver; pin both in CI
Chrome binary not found Chrome is absent or installed outside the expected path Install the supported browser or set the documented binary location, then print the resolved capabilities
Final URL is a login or challenge endpoint Authentication, consent or WAF flow redirected navigation Save redirect locations and cookies; complete the supported flow or request an allowlist
HTML says “Access Denied” but Selenium has no HTTP status Page navigation exposed a document, not a status API Use performance logging or an external network capture for status and headers
Headed works; headless fails Header, client-hint, viewport, timing or graphics differences Run a controlled diff; capture signals before changing options
Local works; CI fails Different proxy, DNS, TLS policy, account or egress IP Compare network identity and environment variables, not just Python code
Blank page or timeout Load failure, blocked resource, network policy or application error Save screenshot, source, console and timing; test DNS/TLS and resource blocking separately

What not to treat as a guaranteed fix

There is no universal Chrome flag that defeats WAFs. Disabling or spoofing navigator.webdriver, forging headers, rotating proxies and solving CAPTCHAs can violate a site’s policy, create new inconsistencies, or fail as detection changes. They also destroy the clean comparison you need for diagnosis. Use them only where the site owner explicitly authorizes the behavior and provides a supported procedure; otherwise use an API or request an allowlist.

Make the investigation reproducible

  1. Pin the Python, Selenium, Chrome and ChromeDriver versions in the failing environment.
  2. Store a redacted diagnostics bundle for one headed and one headless run.
  3. Change one variable at a time: mode, proxy, account, viewport or locale.
  4. Wait for a meaningful selector or network-idle condition instead of relying on an arbitrary sleep when the application supports it.
  5. Stop after a small number of controlled attempts to avoid triggering rate limits.
  6. Share status, headers and timestamps with the site or WAF operator, removing cookies, tokens and personal data.
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 your goal is a clean image or PDF rather than browser-level diagnosis, ScreenshotNeo provides a single screenshot request and an MCP server for AI agents. It accepts consent banners before capture 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 the response reports the page verdict and billing result in X-Page-Verdict and X-Billed headers. Its MCP tools are take_screenshot, get_page_info and capture_pdf, usable from Claude, Cursor and other MCP clients.

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.

See the ScreenshotNeo API documentation for all options. A cURL request:

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

The same call in 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)

And 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 includes full-page captures with lazy images, CSS-selector element captures, dark mode, device presets and custom viewports, retina scale, PDF paper and page controls, custom CSS and JavaScript, clicks and waits, ad/tracker/request blocking, headers, cookies, user agents, Authorization, timezone, geolocation, transparent backgrounds, resizing, selectable-TTL caching, signed links, asynchronous webhooks, bulk capture of up to 100 URLs per call, a usage API and an OpenAPI specification. Parameter names used by other screenshot APIs also work, which can simplify migration.

Every feature is on every plan: 1,000 screenshots per month are free with no card; paid plans start at $5 for 3,000, with higher plans of $15 for 15,000, $39 for 60,000, $99 for 250,000 and $249 for 1,000,000. Yearly billing gives two months free. Create a free ScreenshotNeo account to start with the no-card 1,000-shot allowance.

FAQ

Can Selenium tell me the exact 403 status from driver.get()?

Not reliably. Selenium gives you the rendered result and browser state; use performance logging or an external network capture when an authoritative status code, response headers or redirect chain is required.

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

Should I switch to the old headless implementation?

No. Current Chrome uses unified headless and headful modes; since Chrome 132 the old implementation is a separate chrome-headless-shell binary. Switching implementations changes the experiment instead of explaining the denial.

Does a successful headed run prove the account is allowed?

No. Headed Chrome may use a different cached session, proxy, IP, viewport or timing. Reproduce the same account and network identity before drawing that conclusion.

When should I contact the site operator?

After preserving one redacted headed/headless comparison with timestamps, final URL, status evidence, headers and outbound IP. Ask for an automation allowlist or official API rather than repeatedly modifying detection signals.

Frequently Asked Questions

Can Selenium tell me the exact 403 status from driver.get()?

Not reliably; use performance logging or an external network capture for status and headers.

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

Should I switch to the old headless implementation?

No. Unified headless is the current Chrome mode; the old implementation is a separate chrome-headless-shell binary since Chrome 132.

Does a successful headed run prove the account is allowed?

No. Compare account, proxy, IP, viewport, timing and session state before concluding that headless alone caused the denial.

When should I contact the site operator?

Once you have a redacted headed/headless comparison with timestamps, status evidence, headers and outbound IP; request an allowlist or official API.

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.

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.