How to fix playwright install SSL certificate errors behind a proxy
You run npx playwright install, the Downloading Chromium line appears, and a second later it dies with Error: self signed certificate in certificate chain. Sometimes it says unable to verify the first certificate or unable to get local issuer certificate instead. Chrome opens the same download host fine, and curl might too. That mismatch is what makes it annoying.
This is the question in microsoft/playwright issue 19622, and the answer is short. Your network has a proxy that decrypts and re-signs HTTPS, Node does not trust the certificate authority (CA) that signs for it, and Playwright’s downloader runs on Node. Point NODE_EXTRA_CA_CERTS at the proxy’s root certificate and the install goes through.
This guide is for anyone deploying browser agents or test runners on a corporate network, a firewalled VM or a locked-down CI runner. By the end, playwright install completes, the fix is baked into your Docker image, and the browser trusts the proxy too. It only applies where your own organisation runs the inspecting proxy and IT has signed off on it. On a network you don’t own, this is not your fix.
What you need
- a current Node LTS with npm. Python users can run
pip install playwrightinstead, because its bundled driver is Node and the same fix applies. Playwright is free and open source. - the proxy host and port, plus credentials if it asks for them.
- the root certificate of the inspecting proxy, as a PEM file. Your IT or security team owns it. It costs nothing, but it can take a few days to get, so ask first.
opensslandcurlfor the diagnosis steps. Windows 10 ships curl, and Git for Windows brings openssl.- Docker or CI access if this is going onto an agent fleet. On a new Ubuntu LTS, check whether playwright supports it yet first, since OS support is a separate question from certificates.
Step by step
1. Confirm it is a certificate error
Run the install with Playwright’s debug logging on.
DEBUG=pw:install npx playwright install chromium
On PowerShell, set $env:DEBUG = "pw:install" first, then run the same command.
Expected: the download URL, then one of the three errors above. As of September 2026 the URL points at cdn.playwright.dev, and older releases used azureedge.net hosts, so read the host from your own log.
If it breaks: ETIMEDOUT or ENOTFOUND means the request never reached TLS, so go to step 2. A 407 means the proxy wants credentials. ERR_TUNNEL_CONNECTION_FAILED shows up in the browser at runtime, is a refused CONNECT, and needs a different fix, which I wrote up in this tunnel error guide.
2. Tell Playwright about the proxy
export HTTPS_PROXY="http://proxy.corp.example:3128"
export HTTP_PROXY="$HTTPS_PROXY"
export NO_PROXY="localhost,127.0.0.1,.corp.example"
As of September 2026 the Playwright browsers docs cover installing behind a firewall or proxy, and HTTPS_PROXY is the variable they use. The value starts with http:// on most proxies, even though the traffic inside is HTTPS.
Expected: if you were getting timeouts, you now get the certificate error instead.
If it breaks: a password containing @ or # has to be percent-encoded, so p@ss becomes p%40ss.
3. Find out who is signing
openssl s_client -connect cdn.playwright.dev:443 -servername cdn.playwright.dev \
-proxy proxy.corp.example:3128 </dev/null 2>/dev/null \
| openssl x509 -noout -issuer -subject
Expected: an issuer that is your company, not a public CA. Something like this (names invented):
issuer=O = Example Corp, CN = Example Corp TLS Inspection CA
subject=CN = cdn.playwright.dev
Node’s “self signed certificate in certificate chain” means the chain ends in a root it has never heard of, and here that root is your company’s. The proxy mints a certificate for cdn.playwright.dev on the fly and signs it with its own CA.
If it breaks: the -proxy flag needs OpenSSL 1.1.0 or newer. Without it, run curl -v through the proxy and read the issuer: line. If the issuer is a public CA you recognise, nothing is intercepting and the problem is elsewhere, so check the system clock first.
4. Get the root certificate as PEM
Ask IT for the root CA, plus any intermediate. Where IT keeps it depends on the vendor (Zscaler, Netskope and Squid with ssl_bump all re-sign this way), and I can’t give you a click path for each. If IT already pushes the CA to your Windows laptop, you can export it yourself: certmgr.msc, Trusted Root Certification Authorities, right click the company root, All Tasks, Export, then pick “Base-64 encoded X.509”.
A PEM file starts with -----BEGIN CERTIFICATE-----. If yours is binary DER, convert it, and note the expiry date because you will want it later.
openssl x509 -inform der -in corp-root.cer -out corp-ca-bundle.pem
openssl x509 -in corp-ca-bundle.pem -noout -subject -enddate
Expected: the subject is your company’s root, and you get an expiry date. If it breaks: when step 3 showed an intermediate as the issuer, append it with cat corp-intermediate.pem >> corp-ca-bundle.pem.
5. Prove the file with curl first
curl -sS --cacert corp-ca-bundle.pem -x "$HTTPS_PROXY" -o /dev/null -w "%{http_code}\n" https://cdn.playwright.dev/
Expected: an HTTP status code prints. Even a 403 or 404 is fine, because it means the TLS handshake passed.
If it breaks: curl: (60) SSL certificate problem means the file is wrong or incomplete. Go back to step 3 and compare the issuer with the subject in your file. This is easier to debug than Playwright’s one-line error.
6. Set NODE_EXTRA_CA_CERTS and run the install
export NODE_EXTRA_CA_CERTS="$PWD/corp-ca-bundle.pem"
npx playwright install chromium
On PowerShell:
$env:NODE_EXTRA_CA_CERTS = "C:\certs\corp-ca-bundle.pem"
npx playwright install chromium
Expected: a progress bar, then Chromium lands in ~/.cache/ms-playwright (on Windows, %USERPROFILE%\AppData\Local\ms-playwright). The Node CLI docs say the file adds to Node’s built-in CA list and is read once at process start, so setting it from inside a running script does nothing.
Node ignores your operating system’s certificate store by default.
That is the root cause. IT installed the CA into the Windows or Linux store, so browsers and curl trust it, and Node doesn’t look there. As of September 2026, Node 22.15 and newer (and 23.8 and newer) can read the OS store with NODE_USE_SYSTEM_CA=1. I still prefer the explicit file for containers because it is one path you can audit, and I haven’t tested the system-store route on a locked-down Windows fleet.
If it breaks: sudo usually resets the environment, so sudo npx playwright install silently drops HTTPS_PROXY and NODE_EXTRA_CA_CERTS. Run it without sudo, or use sudo -E. If Node prints “Ignoring extra certs”, the file is not valid PEM.
7. Bake it into Docker and CI
FROM node:22-bookworm
COPY corp-ca-bundle.pem /usr/local/share/ca-certificates/corp-ca.crt
RUN update-ca-certificates
ENV NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/corp-ca.crt
WORKDIR /app
COPY package*.json ./
RUN npm ci && npx playwright install --with-deps chromium
The ENV line sits before npm ci, so npm trusts the proxy too. Build with the proxy passed in as both upper and lower case build args, because tools disagree on which they read and --with-deps runs apt-get:
docker build --build-arg HTTPS_PROXY="$HTTPS_PROXY" --build-arg https_proxy="$HTTPS_PROXY" \
--build-arg HTTP_PROXY="$HTTPS_PROXY" --build-arg http_proxy="$HTTPS_PROXY" -t agent .
Expected: the build finishes and update-ca-certificates reports 1 added. On a self-hosted CI runner, bake the same file into the runner image and set the variable at job level.
If it breaks: 0 added means the file has no .crt extension or is not PEM. If Chromium then refuses to start with an executable-doesn’t-exist error, that is a separate problem, covered in failed to launch chromium in a docker container.
8. Make the browser trust the proxy too
The install fix only covers downloads. At runtime Chromium does its own certificate verification, so an agent loading pages through the inspecting proxy sees ERR_CERT_AUTHORITY_INVALID. Browser traffic is also configured separately, through the proxy launch option (Playwright network docs).
const browser = await chromium.launch({
proxy: { server: "http://proxy.corp.example:3128" },
});
On Windows and macOS, Chromium uses the OS store, so a CA that IT pushed is already trusted. On Linux it reads the NSS database:
apt-get install -y libnss3-tools
mkdir -p "$HOME/.pki/nssdb"
certutil -d "sql:$HOME/.pki/nssdb" -N --empty-password
certutil -d "sql:$HOME/.pki/nssdb" -A -t "C,," -n corp-ca -i /usr/local/share/ca-certificates/corp-ca.crt
The quick alternative is ignoreHTTPSErrors: true on the browser context. It works, but it switches certificate checking off for every site the agent visits, so I only use it on throwaway test runs.
Expected: page.goto("https://example.com") loads with no certificate error. If it breaks: NSS is per user, so the account running Chromium must own the database. Run certutil -d "sql:$HOME/.pki/nssdb" -L as that account and look for corp-ca.
Common pitfalls
- setting
NODE_TLS_REJECT_UNAUTHORIZED=0and moving on. It works, and Node prints a warning that it makes TLS connections insecure. It also turns verification off for everything in that process, including calls that carry your model provider API keys, and it has a habit of surviving into the production image. I don’t use it, not even in staging. - trusting the certificate the proxy presented instead of the CA that issued it. The proxy mints a fresh certificate per host, so you fix cdn.playwright.dev and break on the next domain. Trust the issuer, and get it from IT.
- fixing one downloader and forgetting the rest. npm, pip, apt and git each have their own setting:
npm config set cafile,pip config set global.cert, the system store for apt, andgit config http.sslCAInfo. Check all four before you blame Playwright. - treating every SSL error in the stack as the same bug. A LangGraph Postgres checkpointer failing with an SSL error goes through a different trust path than Node’s downloader, and I wrote that one up in the langgraph postgres ssl guide.
Scaling this
- 10x: the Dockerfile above is enough. Put the CA file and proxy variables into one shared base image so nobody pastes exports into shells anymore.
- 100x: a hundred containers each pulling browser builds through a decrypting proxy is a lot of repeated traffic. Mirror the builds on an internal artifact server and point
PLAYWRIGHT_DOWNLOAD_HOSTat it, or bake the browsers into the image so running containers never download anything. Add a preflight to agent startup that fails fast: CA file readable, the curl test from step 5 passes, andopenssl x509 -checkend 2592000 -noout -in corp-ca-bundle.pemsucceeds, which means the CA has more than 30 days left. - 1000x: the CA becomes infrastructure. An expired root turns into a fleet-wide outage on a known date, so keep the certificate in config management and alert well before expiry. Every agent probably leaves through the same corporate address, which matters for the sites you are allowed to visit, so read how IP reputation is earned and lost. And you will want traces and replays to tell a certificate failure from a site failure, which is what observing AI agents covers.
Where to go next
- browser agent production checklist, the wider list to run through before a fleet goes live.
- Chrome for Testing vs open-source Chromium for a Playwright agent fleet, useful when you decide which browser build to mirror internally.
- failed to launch chromium in a docker container, for when the download works but the browser still won’t start.
The full index is at /blog/.
Written by Xavier Fok
disclosure: this article may contain affiliate links. if you buy through them we may earn a commission at no extra cost to you. verdicts are independent of payouts. last reviewed by Xavier Fok on 2026-09-21.