TL;DR
- Chrome remote debugging is the bridge between a running Chrome instance and external tools such as Chrome DevTools, Puppeteer, and Playwright.
- In this guide, you'll learn how to start Chrome with remote debugging enabled, how to connect to the Chrome remote debugging port, and how to inspect targets from
chrome://inspector from code. - We'll also cover what changed in Chrome 136 and when to stop managing your own remote debugging sessions and switch to an automation solution instead.
Introduction
Chrome remote debugging is the mechanism that lets you inspect, profile, and control a running Chrome browser from outside the browser itself. It underpins headless automation, mobile debugging, and the DevTools plumbing that tools such as Puppeteer and Playwright rely on every day.
It's a powerful solution, but comes with a few details worth getting right, including:
- How you launch Chrome.
- How the remote debugging port behaves.
- How to connect safely.
- What changed recently around profile security.
Once you've decided those points, you can use the feature confidently in local development and know when to move beyond it. This guide will cover exactly how you can get started with Chrome remote debugging.
What is Chrome remote debugging?
At a practical level, Chrome remote debugging exposes a Chrome instance over a local WebSocket endpoint using the Chrome DevTools Protocol (CDP). External tools can then inspect a web page, read console output, intercept network requests, execute JavaScript code, set breakpoints, and control browser behavior programmatically.
The Chrome browser you open manually and the browser controlled by an automation script use the same underlying remote debugging capabilities.
How to start Chrome with remote debugging
To start Chrome with remote debugging, you need a single flag: --remote-debugging-port=9222.
That flag opens the Chrome remote debugging port on your machine, usually at localhost:9222, so DevTools or another client can connect. You can use the same launch pattern across Mac, Linux, and Windows.
# macOS
open -a "Google Chrome" --args --remote-debugging-port=9222 --user-data-dir=/tmp/chrome-debug
# Windows
start chrome --remote-debugging-port=9222 --user-data-dir=%TEMP%\chrome-debug
# Linux
google-chrome --remote-debugging-port=9222 --user-data-dir=/tmp/chrome-debug
For automation, add --headless when you don't need a visible browser window.
On Chrome 136 and later, there's one more practical change to remember: Chrome won't honor the remote debugging switches against the default profile, so use a throwaway --user-data-dir for local debugging sessions.
Depending on your Chrome version and client tooling, you may also run into local examples that add --remote-allow-origins=* as a compatibility workaround.
Now you have a debug-enabled browser, you can connect through Chrome DevTools, hit the JSON endpoints directly, or attach from automation code.
Connecting to the Chrome remote debugging port
Once Chrome is listening, enabling remote debugging in Chrome becomes less about a setting in the UI and more about connecting to the endpoint you just exposed.
The easiest manual path is chrome://inspect, or more specifically chrome://inspect/#devices when you want Chrome's inspect page to discover network targets or USB devices.
If you want the raw target data, open http://localhost:9222/json to get a list of debuggable tabs as JSON, or call /json/version to fetch the browser-level webSocketDebuggerUrl, which is the endpoint many libraries use under the hood.
curl http://127.0.0.1:9222/json/version
Or, if you want to open a raw WebSocket connection from Node.js:
import WebSocket from "ws";
const version = await fetch("http://127.0.0.1:9222/json/version").then((r) =>
r.json(),
);
const ws = new WebSocket(version.webSocketDebuggerUrl);
ws.on("open", () => {
ws.send(JSON.stringify({ id: 1, method: "Browser.getVersion" }));
});
ws.on("message", (data) => {
console.log(data.toString());
});
Puppeteer and Playwright can connect to that endpoint for you, so you do not need to work directly with the protocol details. Puppeteer can use browserWSEndpoint, while Playwright can use chromium.connectOverCDP() with either the HTTP endpoint or the full WebSocket URL.
// Puppeteer
import puppeteer from "puppeteer-core";
const { webSocketDebuggerUrl } = await fetch(
"http://127.0.0.1:9222/json/version",
).then((r) => r.json());
const browser = await puppeteer.connect({
browserWSEndpoint: webSocketDebuggerUrl,
});
// Playwright
import { chromium } from "playwright-core";
const browser = await chromium.connectOverCDP("http://127.0.0.1:9222");
Remote debugging Chrome on Android
For remote debugging Chrome on Android, start by enabling Developer Options and USB debugging on the Android device.
Next, connect it over USB, open Chrome on your development machine, go to chrome://inspect/#devices, and make sure Discover USB devices is enabled.
Once the phone appears, click Inspect next to the tab you want to debug and Chrome opens a new DevTools instance for that remote session.
This workflow gives you a more accurate picture than emulation alone. You're testing real device performance, real network behavior, actual rendering, touch input, and the JavaScript errors your users see on mobile devices. When you need the mobile persona at scale rather than one phone on your desk, Browserless exposes Android emulation via emulationOs=android.
From the Elements and Network panels, you can inspect elements, analyze traffic, and set breakpoints the same way you would on desktop.
If your app uses WebView, the same chrome://inspect screen can also show debug-enabled WebViews after you enable WebView debugging in the app itself – useful when the bug lives inside embedded browser content rather than a regular Chrome tab.
As handy as that is, it highlights the trade-off too: remote debugging is powerful enough to control real browser state, so security becomes an important consideration.
Security and the remote debugging port
Treat the remote debugging port as sensitive. Anything that can reach it can do far more than read logs – it can inspect pages, run commands, manipulate browser state, and interact with tabs through CDP. You should bind it to local use only and never expose --remote-debugging-port on a public interface.
Chrome 136 tightened that model further, which is why the launch commands above all pass a throwaway --user-data-dir. Switches against the default data directory are ignored outright. Chrome is trying to reduce the risk of an attacker attaching to a real user profile and pulling useful state out of it. Note that this limits what an attacker would find, not who can connect. The port has no authentication of its own, so anything that can reach it has full control, and keeping it unreachable is the protection.
Once you move to a hosted browser, the same principle applies to the token in your connection URL. It's a credential, so keep it in an environment variable rather than in committed code, use separate tokens per environment, and rotate them if one leaks.
For day-to-day local work, that's manageable. The problem shows up when your one-off debugging setup turns into shared infrastructure, multiple users, long-running workers, or automation that needs to stay stable under load.
Chrome remote debugging in headless automation
Managing Chrome remote debugging locally works well in development, but it starts becoming infrastructure once you need to run it at scale.
Puppeteer can attach with puppeteer.connect(), Playwright can attach with chromium.connectOverCDP(), and both approaches work well when you're testing locally or troubleshooting a single Chrome instance.
import puppeteer from "puppeteer-core";
const browser = await puppeteer.connect({
browserWSEndpoint: "ws://127.0.0.1:9222/devtools/browser/<id>",
});
puppeteer-core works here because Chrome is already running, there's no need for the browser binary that full Puppeteer downloads on install.
At scale, though, managing your own remote debugging sessions gets messy. You have to launch Chrome reliably, isolate profiles, protect the debugging endpoint, deal with concurrency, and keep browser versions patched.
Browserless is the managed version of the same idea: it exposes browsers over WebSocket so you can keep using Puppeteer or Playwright, but without running and securing the fleet yourself.
Browserless also fits naturally if your team already writes CDP-based automation. Our Browsers as a Service product lets you swap a local endpoint for a hosted WebSocket connection, while keeping the same connection pattern your code already uses. Compared with the local snippet above, only the endpoint changes:
import puppeteer from "puppeteer-core";
const browser = await puppeteer.connect({
browserWSEndpoint: `wss://production-sfo.browserless.io/chromium?token=${process.env.BROWSERLESS_TOKEN}`,
});
Conclusion
Chrome remote debugging is still worth understanding even if you eventually outsource the plumbing. Knowing how the port works, how targets are discovered, and where the sharp edges are will save you time in both manual debugging and production automation.
In practice, the workflow is straightforward: launch Chrome with --remote-debugging-port, connect through chrome://inspect or the CDP endpoint, use it for Android debugging or headless automation, and keep the security caveats in mind – especially on newer Chrome versions.
If your team is using Chrome remote debugging to power Puppeteer or Playwright at scale, Browserless gives you managed Chrome instances over WebSocket CDP endpoints, so you don't have to configure local Chrome, manage ports, or worry about exposing a debugging endpoint yourself. Try Browserless free and keep the same automation model, without the operational drag.
Chrome remote debugging FAQs
What is the Chrome DevTools Protocol (CDP) and how does remote debugging use it?
Chrome remote debugging exposes a running Chrome instance over a local WebSocket endpoint using the Chrome DevTools Protocol (CDP). Through that endpoint, external tools can inspect a page, read console output, intercept network requests, execute JavaScript, set breakpoints, and control the browser programmatically. It's the same underlying mechanism whether you open Chrome manually or drive it with an automation script, and it's what tools like Chrome DevTools, Puppeteer, and Playwright rely on to talk to the browser.
How do I enable Chrome remote debugging with --remote-debugging-port=9222?
Launch Chrome with a single flag, --remote-debugging-port=9222, which opens the debugging port at localhost:9222 so a client can connect. The same pattern works on macOS, Windows, and Linux, with --headless added when you don't need a visible window. See the How to start Chrome with remote debugging section above for the exact command on each platform. One thing to watch: on Chrome 136 and later, the remote debugging switches are ignored against the default profile, so if the port isn't working, pair the flag with a throwaway --user-data-dir pointing at a non-standard profile.
How do I connect to the Chrome remote debugging port?
The easiest manual route is chrome://inspect (or chrome://inspect/#devices). For the raw target data, open http://localhost:9222/json to list debuggable tabs, or call /json/version to get the browser-level webSocketDebuggerUrl that most libraries use under the hood. From code you can open that WebSocket directly, or let a library do it: Puppeteer connects with browserWSEndpoint, and Playwright connects with chromium.connectOverCDP("http://localhost:9222"), so you don't have to handle the protocol details yourself.
How do I remotely debug Chrome on Android?
Enable Developer Options and USB debugging on the Android device, connect it over USB, then open chrome://inspect/#devices on your development machine and make sure "Discover USB devices" is checked. When the phone appears, click Inspect next to the tab you want and Chrome opens a DevTools window for that remote session. This tests real device performance, network behavior, rendering, touch input, and the JavaScript errors your mobile users actually see, which is more accurate than emulation. If your app uses a WebView, the same screen can inspect it once you enable WebView debugging in the app.
Why is my remote debugging port not working on Chrome 136?
Starting with Chrome 136, the remote debugging switches are no longer honored when you point them at the default Chrome profile. If you launch with --remote-debugging-port alone and nothing connects, that default-profile restriction is almost always the cause. The fix is to pair the flag with a --user-data-dir that points at a non-standard, throwaway profile, for example --remote-debugging-port=9222 --user-data-dir=/tmp/chrome-debug. Chrome added this restriction to reduce the risk of an attacker attaching to a real user profile and pulling useful state out of it, so the extra flag is expected behavior rather than a bug.
Is Chrome remote debugging safe?
Treat the remote debugging port as sensitive. Anything that can reach it can do far more than read logs, since it can inspect pages, run commands, manipulate browser state, and interact with tabs through CDP. Keep it bound to local use only and never expose --remote-debugging-port on a public interface. Chrome 136 tightened this further by ignoring the debugging switches against the default profile and requiring a non-standard --user-data-dir, which limits what an attacker could reach. For local development this is easy to manage, but once remote debugging turns into shared infrastructure with multiple users or long-running workers, you can offload the isolation and endpoint security to a managed browser service like Browserless.