# netdash > netdash is proxy infrastructure for developers, AI agents and data teams. Residential and mobile proxies, sold per GB, share one credential pair, one username syntax, country targeting and rotating or sticky sessions over standard HTTP. Static ISP and datacenter IPs are available through the sales team. Purchased GB never expire. ## Connection facts (verified) - Gateways: solve-{us|eu}.netdash.io:1000, scale-{us|eu}.netdash.io:2000, opus-{us|eu}.netdash.io:3000, mobile-{us|eu}.netdash.io:4000. `-us` is the Americas gateway, `-eu` the Europe gateway. - Username: `{USERNAME}-cc-{CC}[-session-{SESSION_ID}-ttl-{MINUTES}]`. ISO 3166-1 alpha-2, uppercase. Use GB for the United Kingdom, not UK. - Rotating: omit the session part. Sticky: add `-session-{id}-ttl-{minutes}` (dashboard presets: 15 minutes, 30 minutes, 12 hours, 7 days (10,080 minutes)). - Auth: One username and password per account for every residential and mobile plan; each plan keeps its own GB balance. ISP and datacenter orders come with their own credentials in the delivered list. The password is the proxy password shown in the dashboard, not the sign-in password. - Protocol: HTTP proxy. HTTPS websites are reached through the proxy with HTTP CONNECT tunnelling. - Targeting: Country-level targeting with -cc-{CC}. - Capacity: No per-account concurrency or thread limit. - Billing: One-time GB purchases in USD with graduated pricing per order: each GB is charged at the rate of the band it falls into. 1 to 50,000 GB per order. Purchased residential and mobile GB do not expire. - ISP and datacenter: Billed per IP, renews every 30 days. Cancel in the billing portal before renewal. Self-serve checkout for ISP and datacenter proxies is paused. - Not offered: CAPTCHA solving, JavaScript rendering or automatic retries (netdash forwards your traffic unchanged); City, state, ZIP, ASN or carrier targeting (targeting is country-level); IP allowlisting (authentication is username and password); A public account API or SDK (none is needed: it is a standard HTTP proxy); Published uptime figures or an SLA. - Credentials and the proxy generator live in the dashboard at https://app.netdash.io (Residential and Mobile pages). Example (rotating, US exit, Scale plan): ```bash curl --max-time 30 -x "http://USERNAME-cc-US:PASSWORD@scale-us.netdash.io:2000" \ "https://api.ipify.org?format=json" ``` ## Docs (Markdown) - [Docs](https://netdash.io/docs/index.md): netdash documentation: quickstart, proxy string format and endpoints, rotating and sticky sessions, country targeting, troubleshooting and billing. - [Quickstart](https://netdash.io/docs/quickstart.md): netdash quickstart: create an account, buy a plan from 1 GB, copy your credentials and send a first proxied request with cURL, Python or Node.js. - [Proxy format](https://netdash.io/docs/proxy-format.md): The netdash proxy string format: gateway hosts and ports per plan, the username syntax for country and sessions, and the three output formats, with examples. - [Sessions](https://netdash.io/docs/sessions.md): How sticky session proxy usernames work on netdash: session IDs, TTL in minutes, dashboard presets from 15 minutes to 7 days, and when to rotate instead. - [Country targeting](https://netdash.io/docs/targeting.md): Proxy country targeting on netdash: add -cc- and an ISO 3166-1 code to the username, use GB for the United Kingdom, and pick the matching regional gateway. - [Troubleshooting](https://netdash.io/docs/troubleshooting.md): Fix a proxy 407 error, HTTPS requests that skip the proxy, timeouts, the wrong exit country and shell quoting problems, with the exact cause for each. - [Billing](https://netdash.io/docs/billing.md): netdash billing explained: one GB balance per plan that never expires, graduated pricing per order, card payments via Stripe and 30-day ISP renewals. ## Docs for AI agents - [Setup prompt for coding agents](https://netdash.io/developers/claude-code): copyable instructions that wire netdash into a project with credentials from the environment - [Claude Code and Playwright MCP](https://netdash.io/developers/claude-code.md): per-client proxy configuration that keeps the agent's own model traffic direct - [Agent frameworks](https://netdash.io/developers/agent-frameworks.md): Browser Use, OpenAI Agents SDK, LangChain ## Integration guides (Markdown) - [Python](https://netdash.io/developers/python.md): Python requests proxy setup with both http and https keys, plus httpx and aiohttp: authentication, sticky sessions, country targeting and common errors. - [Node.js](https://netdash.io/developers/nodejs.md): Node.js proxy setup with undici ProxyAgent and fetch, or axios with https-proxy-agent. Why built-in fetch ignores HTTPS_PROXY by default, and what to use. - [Go (net/http)](https://netdash.io/developers/go.md): Go HTTP proxy setup with net/http: an authenticated proxy URL in a custom Transport, sticky sessions per client, timeouts and the errors to handle. - [Scrapy](https://netdash.io/developers/scrapy.md): Scrapy proxy setup through request meta or a small downloader middleware, with credentials from the environment, sticky sessions and polite crawl settings. - [Selenium](https://netdash.io/developers/selenium.md): Selenium proxy setup with authentication: why Chrome takes no credentials in --proxy-server, and a tested local forwarder for WebDriver sessions. - [Playwright](https://netdash.io/developers/playwright.md): Playwright proxy setup for Node.js and Python: authenticate per browser or per context, pin a sticky session for multi-step flows, and set the exit country. - [Puppeteer](https://netdash.io/developers/puppeteer.md): Puppeteer proxy setup with --proxy-server and page.authenticate, per-context proxies with createBrowserContext, sticky sessions and the usual 407 causes. - [Claude Code](https://netdash.io/developers/claude-code.md): Use proxies in Claude Code projects: keep credentials in .env, configure each client instead of HTTPS_PROXY, and give Playwright MCP an authenticated proxy. - [Agent frameworks](https://netdash.io/developers/agent-frameworks.md): AI agent framework proxy setup for Browser Use, the OpenAI Agents SDK, LangChain and Playwright MCP: proxy the browser and tools, not the model API calls. - [Cloud browsers](https://netdash.io/developers/cloud-browsers.md): Cloud browser proxy setup in three layers: browsers on VMs or containers, the netdash HTTP proxy, and hosted browser services that accept your own proxy. ## Products and pricing - [Pricing](https://netdash.io/pricing): Residential proxy pricing per GB for the Solve, Scale and Opus plans, plus mobile: graduated per order from 1 to 50,000 GB, with a calculator. GB never expire. - [Residential proxies](https://netdash.io/products/residential-proxies): Residential proxies over HTTP with country targeting and rotating or sticky sessions. Three plans, one credential pair and graduated pricing per GB. - [Mobile proxies](https://netdash.io/products/mobile-proxies): Mobile proxies with country targeting and rotating or sticky sessions, using the same credentials and username syntax as residential, billed per GB. - [ISP proxies](https://netdash.io/products/isp-proxies): ISP proxies (static residential IPs) in Ashburn, Frankfurt, London and Sydney, billed per IP every 30 days. Self-serve checkout is paused; contact sales. - [Datacenter proxies](https://netdash.io/products/datacenter-proxies): Static datacenter proxies in the US, Germany, the UK and the Netherlands, billed per IP every 30 days, /24 subnets in the US and UK. Contact sales to order. - [Compare proxy types](https://netdash.io/products/compare): Compare proxy types side by side: residential, mobile, ISP and datacenter on billing, sessions, targeting and availability, and what netdash doesn't do. ## Use cases - [AI agents](https://netdash.io/solutions/ai-agents): Proxies for AI agents: give each agent run an exit country and a sticky session in its browser or HTTP client. Guides for Claude Code and agent frameworks. - [Browser automation](https://netdash.io/solutions/browser-automation): Browser automation proxies for Playwright, Puppeteer and Selenium: a sticky session per browser context, credentials passed correctly, fewer GB per page. - [Web data collection](https://netdash.io/solutions/web-scraping): Web scraping proxies for scheduled collection of public pages by country: rotating or sticky sessions, no per-account concurrency limit, GB that never expire. - [E-commerce and price monitoring](https://netdash.io/solutions/ecommerce): Price monitoring proxies for public product prices, currency and availability by country. A SKU-by-market workflow, configuration and a Scrapy spider. - [SEO monitoring](https://netdash.io/solutions/seo-monitoring): SERP tracking proxies that query search results from an exit IP in each market you track. Country-level targeting, a rank-check workflow and Python code. - [Ad verification](https://netdash.io/solutions/ad-verification): Ad verification proxies for checking your own campaigns by country: landing pages, redirect chains and mobile placements, with mobile and residential exits. - [Localization and QA](https://netdash.io/solutions/localization-testing): Localization testing proxies for QA: check language, currency, consent banners and geo-redirects as visitors in each country see them, with Playwright tests. - [Security testing](https://netdash.io/solutions/security-testing): Proxies for security testing of systems you own or are authorized to test: verify geo rules, rate limits and WAF policies from exit IPs in other countries. - [Brand protection](https://netdash.io/solutions/brand-protection): Brand protection proxies for finding counterfeit listings and unauthorized sellers on public pages by country, with evidence capture you can reproduce. - [Travel data](https://netdash.io/solutions/travel): Travel fare aggregation proxies for public flight fares and hotel rates by point of sale. Sticky sessions keep each search sequence on one exit IP. ## Policies - [Responsible use](https://netdash.io/responsible-use): Responsible proxy use: what the netdash Terms prohibit, how to collect public web data responsibly, guardrails for AI agents, and how to report misuse. - [Terms of Service](https://netdash.io/legal/terms): The netdash Terms of Service: the agreement that governs your use of the netdash website and proxy services, including the Ethical Use Policy and billing. - [Privacy Policy](https://netdash.io/legal/privacy): The netdash Privacy Policy: which personal information is collected when you use the netdash website and services, how it is used and protected, and contacts. - [Refund Policy](https://netdash.io/legal/refund): The netdash Refund Policy: which purchases can be refunded, the conditions that apply to a refund, and how to send a refund request to the billing team. ## Optional - [What is a residential proxy?](https://netdash.io/blog/what-is-a-residential-proxy.md): What is a residential proxy? How residential IPs route your requests, how they differ from datacenter IPs, and which sourcing questions to ask a provider. - [Static vs rotating proxies](https://netdash.io/blog/static-vs-rotating-proxies.md): Static vs rotating proxies compared by workload: logins and multi-step flows, monitoring, testing and data collection, with session examples you can copy. - [Residential vs datacenter proxies](https://netdash.io/blog/residential-vs-datacenter-proxies.md): Residential vs datacenter proxies compared on IP origin, billing model, consistency and typical workloads, with a decision table instead of benchmarks. - [ISP vs residential proxies](https://netdash.io/blog/isp-vs-residential-proxies.md): ISP vs residential proxies: how static ISP IPs and rotating residential IPs are assigned, billed per IP or per GB, and which workloads suit each one. - [Free vs paid proxies](https://netdash.io/blog/free-vs-premium-proxies.md): Free vs paid proxies: why public proxy lists put your traffic at risk, what an unknown operator can see, and what to check before paying for a service. - [Choosing a rotating proxy provider](https://netdash.io/blog/best-rotating-proxies-2026.md): Looking for the best rotating proxies? Use this 2026 checklist: pricing model, sessions, targeting, documentation, terms and sourcing, without vendor scores. - [Web scraping with Python and proxies](https://netdash.io/blog/web-scraping-python-proxies.md): Web scraping with Python and proxies, end to end: fetch with requests or httpx, parse, store, honor robots.txt and rate limits, and handle errors politely. - [Collecting web data for AI](https://netdash.io/blog/ai-training-data-scraping.md): AI training data scraping, done responsibly: robots.txt and AI crawler rules, site terms, copyright and licensing, personal data, rate limits and records. - [Full documentation in one file](https://netdash.io/llms-full.txt) Contact: sales@netdash.io (sales), support@netdash.io (support), Discord https://discord.gg/vrfAN7hFKx --- # netdash documentation > netdash is a standard HTTP proxy with one credential pair and one username syntax. Start with the quickstart, then use the reference pages. Source: https://netdash.io/docs · Updated 2026-10-09 The netdash documentation covers everything between buying a plan and a working integration: which gateway each plan uses, how the username sets the exit country and the session, what to do when a request fails, and how billing works. Your username and password are the same on every residential and mobile plan, and each plan keeps its own GB balance. Every request goes to a regional gateway with a username built like this one: `USERNAME-cc-US-session-a1b2c3d4-ttl-30`: your username, then `-cc-` and the exit country (ISO code, GB for the UK), then for sticky sessions `-session-` and an ID (letters and digits) and `-ttl-` and the lifetime in minutes. Leave out the session part for rotating sessions. That is the whole interface. There is no SDK to install: any client that supports an HTTP proxy works. HTTPS websites go through a CONNECT tunnel, so the proxy URL starts with `http://` for every website: A first request: ```bash curl --max-time 30 -x "http://USERNAME-cc-US:PASSWORD@scale-us.netdash.io:2000" \ "https://api.ipify.org?format=json" ``` _Checked against a local test proxy._ ## Start here 1. **Create an account**: Sign up with an email address and password and enter the one-time code we email you, or sign in with Google or Discord. ([Create account](https://app.netdash.io/register)) 2. **Buy a plan**: Choose a residential plan or Mobile and buy GB for it, from 1 GB per order. You pay once, by card. ([See pricing](https://netdash.io/pricing)) 3. **Copy your credentials**: In the dashboard, open **Residential** (or **Mobile**) and use **Generate**. It shows your username, password and the gateway for your plan. ([Proxy format reference](https://netdash.io/docs/proxy-format)) 4. **Send a first request**: Run one request through the gateway with curl, Python or Node.js and check the exit IP it prints. ([Read the quickstart](https://netdash.io/docs/quickstart)) ## Reference - [Quickstart](https://netdash.io/docs/quickstart): From a new account to a first proxied request with curl, Python or Node.js. - [Proxy format reference](https://netdash.io/docs/proxy-format): Gateway hosts and ports per plan, the username syntax and the three output formats. - [Sessions](https://netdash.io/docs/sessions): Rotating and sticky sessions, session IDs, and lifetime presets from 15 minutes to 7 days. - [Country targeting](https://netdash.io/docs/targeting): Pick the exit country with an ISO code, use GB for the UK and choose the gateway. - [Troubleshooting](https://netdash.io/docs/troubleshooting): 407 errors, HTTPS requests that skip the proxy, timeouts and the wrong exit country. - [Billing](https://netdash.io/docs/billing): One GB balance per plan, graduated pricing per order and ISP and datacenter renewals. ## Integration guides The [proxy integration guides](https://netdash.io/developers) show the exact setup for each HTTP client, browser automation tool and coding agent, with code you can copy. Five of them: - [Python](https://netdash.io/developers/python): requests and httpx, with both proxy keys set and timeouts on. - [Node.js](https://netdash.io/developers/nodejs): undici ProxyAgent with fetch, and axios with https-proxy-agent. - [Playwright](https://netdash.io/developers/playwright): Proxies per browser or per context, with sticky sessions for multi-step flows. - [Scrapy](https://netdash.io/developers/scrapy): Proxies through request meta or a small downloader middleware. - [Claude Code](https://netdash.io/developers/claude-code): Credentials in .env and a proxy on each client, not on the coding agent. ## What netdash doesn't do netdash forwards your traffic and leaves the rest to your code. These limits apply to every plan: - **No CAPTCHA solving, JavaScript rendering, retries or unblocking**: netdash forwards your traffic unchanged, so a challenge reaches your client exactly as the website sent it. - **Targeting below country level**: You choose the exit country; you can't select a city, region, ZIP code, ASN or carrier. - **Authentication by IP address**: Every request uses your username and password. - **A public account API or SDK**: None is needed: it's a standard HTTP proxy, and usage lives in the dashboard. - **Published uptime figures or an SLA**: We don't publish uptime figures or offer an SLA. ## netdash documentation for AI agents If a coding agent or an AI assistant sets up netdash for you, give it the plain-text versions of these pages instead of the rendered HTML: - [llms.txt](https://netdash.io/llms.txt) lists every docs page and integration guide with a one-line description, plus the connection facts. - [llms-full.txt](https://netdash.io/llms-full.txt) contains the full text of the docs and guides in one file. - Add `.md` to the URL of any docs page or guide for a Markdown copy, for example `/docs/proxy-format.md`. The Markdown copy of this page is `/docs/index.md`. The quickstart also has a [setup prompt for coding agents](https://netdash.io/docs/quickstart#setup-prompt) that you can paste into your agent. It asks the agent to read credentials from environment variables and to configure the proxy on the HTTP client or browser, not on the whole process. ## Get help - [Troubleshooting](https://netdash.io/docs/troubleshooting): The common errors, their causes and the fix for each. - [FAQ](https://netdash.io/faq): Short answers about plans, billing, sessions and setup. - [Contact](https://netdash.io/contact): Email sales or support, live chat on this site, or Discord. - [Service status](https://netdash.io/status): What to check first when requests fail. We don't currently publish a live status page. You can also [email support](mailto:support@netdash.io?subject=Support%20request) directly or ask in the [netdash Discord](https://discord.gg/vrfAN7hFKx). --- # Quickstart: your first proxy request > Create an account, buy a plan, copy your username and password from the dashboard, and send one request through the gateway. Source: https://netdash.io/docs/quickstart · Updated 2026-10-09 This netdash quickstart takes you from a new account to one request through a netdash gateway, in four steps, and then points you to the reference pages. Plans are bought per GB; the smallest order is 1 GB. ## 1. Create an account Sign up with an email address and password and enter the one-time code we email you, or sign in with Google or Discord. [Create account](https://app.netdash.io/register) [Sign in](https://app.netdash.io/login) ## 2. Buy a plan Residential proxies come in three plans: Solve, our lowest-cost residential plan; Scale, our mid-priced plan; and the Opus plan, our highest-priced plan. They differ in which upstream residential pool they route to and in price. The connection syntax is identical, so you can switch plans later by changing the host and port. Mobile is a separate plan with mobile exit IPs and the same username syntax. The [residential proxies](https://netdash.io/products/residential-proxies) page describes the plans in more detail. In the shop, pick a plan and an amount from 1 to 50,000 GB. You pay once, in USD, by card through Stripe Checkout, and the plan is provisioned automatically once payment is confirmed. Prices are graduated per order and listed on the [pricing](https://netdash.io/pricing) page. [Open the shop](https://app.netdash.io/shop) > **ISP and datacenter proxies** Self-serve checkout for static ISP and datacenter proxies is paused. Contact sales about [ISP](mailto:sales@netdash.io?subject=ISP%20proxies%20inquiry&body=Locations%3A%0ANumber%20of%20IPs%3A%0AUse%20case%3A%0A) or [datacenter](mailto:sales@netdash.io?subject=Datacenter%20proxies%20inquiry&body=Locations%3A%0ANumber%20of%20IPs%3A%0AUse%20case%3A%0A) availability. ## 3. Copy your credentials In the dashboard, open **Residential** (or **Mobile** for mobile proxies) and use **Generate**. Choose the plan, the gateway region, the exit country and the session type, and the generator gives you ready-made proxy strings with your username, your password and the gateway host and port. Your base username and password are the same on every residential and mobile plan. Each plan keeps its own GB balance. The examples on this site write `USERNAME` and `PASSWORD` in their place: replace them with your own values. > **Keep credentials out of your code** Keep `NETDASH_USERNAME` and `NETDASH_PASSWORD` in a `.env` file or your secret manager, never in source control or in a chat with an assistant. ## 4. Send your first request with the netdash quickstart Replace `USERNAME` and `PASSWORD`, then run one of these. The example uses the Scale plan on the Americas gateway (`scale-us.netdash.io:2000`) with a US exit and a rotating session: cURL: ```bash curl --max-time 30 -x "http://USERNAME-cc-US:PASSWORD@scale-us.netdash.io:2000" \ "https://api.ipify.org?format=json" ``` Python: ```python import requests proxy = "http://USERNAME-cc-US:PASSWORD@scale-us.netdash.io:2000" proxies = {"http": proxy, "https": proxy} # set both, or HTTPS requests bypass the proxy response = requests.get("https://api.ipify.org?format=json", proxies=proxies, timeout=30) response.raise_for_status() print(response.text) ``` Node.js: ```javascript import { ProxyAgent, fetch } from "undici"; const dispatcher = new ProxyAgent({ uri: "http://scale-us.netdash.io:2000", token: "Basic " + Buffer.from("USERNAME-cc-US:PASSWORD").toString("base64"), }); const res = await fetch("https://api.ipify.org?format=json", { dispatcher, signal: AbortSignal.timeout(30_000) }); console.log(await res.text()); ``` _Checked against a local test proxy._ Three details matter: - **The proxy URL starts with `http://`**, also for HTTPS websites. netdash is an HTTP proxy, and HTTPS websites are reached through a CONNECT tunnel. - **Quote the URL in your shell.** In zsh, an unquoted `?` is a filename pattern, and the command stops with `zsh: no matches found`. - **Configure the proxy on the client.** Python requests picks the proxy by the scheme of the website's URL, so the example sets both the `http` and the `https` key; with only `http`, HTTPS requests go out directly. Node.js's built-in `fetch` doesn't read `HTTPS_PROXY` by default, so the example passes a `ProxyAgent` to `fetch`, both from the `undici` package (`npm install undici`). To try another plan, country or a sticky session, change the options here and copy the updated code. Use the Americas gateway (`-us`) for exits in the Americas and the Europe gateway (`-eu`) for exits in Europe. For any other country, [ask support](mailto:support@netdash.io?subject=Support%20request) which gateway to use and whether the country is available. cURL: ```bash curl --max-time 30 -x "http://USERNAME-cc-US:PASSWORD@scale-us.netdash.io:2000" \ "https://api.ipify.org?format=json" ``` Python: ```python import requests proxy = "http://USERNAME-cc-US:PASSWORD@scale-us.netdash.io:2000" proxies = {"http": proxy, "https": proxy} # set both, or HTTPS requests bypass the proxy response = requests.get("https://api.ipify.org?format=json", proxies=proxies, timeout=30) response.raise_for_status() print(response.text) ``` Node.js: ```javascript import { ProxyAgent, fetch } from "undici"; const dispatcher = new ProxyAgent({ uri: "http://scale-us.netdash.io:2000", token: "Basic " + Buffer.from("USERNAME-cc-US:PASSWORD").toString("base64"), }); const res = await fetch("https://api.ipify.org?format=json", { dispatcher, signal: AbortSignal.timeout(30_000) }); console.log(await res.text()); ``` ## Check the result A working setup prints a JSON object with the exit IP, the address the website sees: Output: ```json {"ip":"203.0.113.10"} ``` Your output shows a different address in the country you chose. With a rotating session the exit IP isn't pinned, so a later run can print another address. If curl prints `CONNECT tunnel failed, response 407` or your client reports `407 Proxy Authentication Required`, the gateway didn't accept the username or password. Check the password, the base username and the parameters: an uppercase country code such as `-cc-US`, and `-ttl-` whenever you use `-session-`. [Troubleshooting](https://netdash.io/docs/troubleshooting) lists every cause. ## Sessions and countries To keep the same exit IP across several requests, add a session ID and a lifetime in minutes. This request exits in Germany through the Europe gateway and keeps its exit IP for 30 minutes while that IP stays available: Sticky session, Germany: ```bash curl --max-time 30 -x "http://USERNAME-cc-DE-session-a1b2c3d4-ttl-30:PASSWORD@scale-eu.netdash.io:2000" \ "https://api.ipify.org?format=json" ``` _Checked against a local test proxy._ Run it twice within 30 minutes: as long as the exit IP stays available, both runs print the same address. Use your own session ID for each task: letters and digits only, no hyphens. [Sessions](https://netdash.io/docs/sessions) explains IDs and lifetimes, and [country targeting](https://netdash.io/docs/targeting) covers country codes and the choice of gateway. ## Setup prompt If a coding agent such as Claude Code writes the integration for you, paste this prompt into it. It gives the agent the gateways and the username syntax, asks it to read credentials from `NETDASH_USERNAME` and `NETDASH_PASSWORD`, to configure the proxy on the HTTP client or browser rather than for the whole process, and to stop instead of retrying when a website shows a challenge. Setup prompt for your coding agent: ```text Add netdash proxy support to this project. Facts (from https://netdash.io/docs/proxy-format.md): - netdash is a standard HTTP proxy (HTTPS sites go through CONNECT). No SDK. - Gateways: Solve plan: solve-{us|eu}.netdash.io:1000; Scale plan: scale-{us|eu}.netdash.io:2000; Opus plan: opus-{us|eu}.netdash.io:3000; Mobile: mobile-{us|eu}.netdash.io:4000. Use -us for exits in the Americas, -eu for Europe. - Username: NETDASH_USERNAME-cc-{COUNTRY} for rotating sessions, or NETDASH_USERNAME-cc-{COUNTRY}-session-{ID}-ttl-{MINUTES} for sticky sessions. COUNTRY is ISO 3166-1 alpha-2 uppercase (GB, not UK). ID is letters and digits only. - Password: NETDASH_PASSWORD (the proxy password from the dashboard, not the sign-in password). Requirements: - Read NETDASH_USERNAME and NETDASH_PASSWORD from the environment (.env, never commit them). Do not hardcode credentials. - Configure the proxy on the HTTP client or browser explicitly. Do not export HTTP_PROXY/HTTPS_PROXY globally in the shell that runs the coding agent, and do not add them to the coding agent's settings env either. - Python requests: set both "http" and "https" in proxies. httpx: use proxy=. Node.js: use undici ProxyAgent with undici fetch. Playwright/Puppeteer: pass the server separately from username and password. Selenium/Chrome takes no proxy credentials: use the local forwarder from https://netdash.io/developers/selenium.md. - Set timeouts, handle HTTP 407 (wrong credentials) with a clear error, and keep TLS verification on. - Add a small check that prints the exit IP from https://api.ipify.org?format=json through the proxy. - netdash does not solve CAPTCHAs. If a site shows a challenge or rate-limits the client, stop and report it instead of retrying. - Only access sites and data the project is permitted to use, respect robots.txt and rate limits. ``` Agents can also read [llms.txt](https://netdash.io/llms.txt), which lists every docs page in plain text. The [Claude Code guide](https://netdash.io/developers/claude-code) shows the full setup for Claude Code projects, including a proxied browser through Playwright MCP. ## Next steps - [Proxy format reference](https://netdash.io/docs/proxy-format): Every endpoint, the username syntax and the three output formats. - [Sessions](https://netdash.io/docs/sessions): When to rotate, when to stick, and how session IDs and lifetimes work. - [Country targeting](https://netdash.io/docs/targeting): ISO country codes, GB for the UK, and which gateway to use. - [Troubleshooting](https://netdash.io/docs/troubleshooting): 407 errors, requests that skip the proxy, timeouts and quoting. - [Billing](https://netdash.io/docs/billing): Per-plan balances, graduated pricing per order and GB that never expire. - [Python guide](https://netdash.io/developers/python): requests and httpx with credentials from the environment. - [Node.js guide](https://netdash.io/developers/nodejs): undici ProxyAgent with fetch, and axios with https-proxy-agent. - [Claude Code guide](https://netdash.io/developers/claude-code): Proxies in Claude Code projects and for Playwright MCP. All [proxy integration guides](https://netdash.io/developers) are listed in the developer section. Stuck? See [troubleshooting](https://netdash.io/docs/troubleshooting) or [email support](mailto:support@netdash.io?subject=Support%20request). ## Sources - [Requests documentation: Advanced Usage, Proxies](https://requests.readthedocs.io/en/latest/user/advanced/#proxies) (accessed 2026-10-09) - [undici documentation: ProxyAgent](https://github.com/nodejs/undici/blob/main/docs/docs/api/ProxyAgent.md) (accessed 2026-10-09) - [Node.js Learn: Enterprise Network Configuration](https://nodejs.org/en/learn/http/enterprise-network-configuration) (accessed 2026-10-09) - [zsh manual: Options (NOMATCH)](https://zsh.sourceforge.io/Doc/Release/Options.html) (accessed 2026-10-09) - [ipify API](https://www.ipify.org/) (accessed 2026-10-09) --- # Proxy string format and endpoints > A netdash proxy string is http://USERNAME-cc-{CC}[-session-{SID}-ttl-{MIN}]:PASSWORD@{plan}-{us|eu}.netdash.io:{port}. This page lists every endpoint and parameter. Source: https://netdash.io/docs/proxy-format · Updated 2026-10-09 Every netdash request names three things: a gateway (host and port), a username with parameters, and your password. The gateway decides the plan and the region, and the username carries the exit country and, for sticky sessions, the session ID and its lifetime. This is the complete proxy string format in one line: Proxy string format: ```text http://USERNAME-cc-{CC}[-session-{SID}-ttl-{MIN}]:PASSWORD@{plan}-{us|eu}.netdash.io:{port} ``` `USERNAME-cc-US-session-a1b2c3d4-ttl-30`: your username, then `-cc-` and the exit country (ISO code, GB for the UK), then for sticky sessions `-session-` and an ID (letters and digits) and `-ttl-` and the lifetime in minutes. Leave out the session part for rotating sessions. ## Endpoints Each plan has two regional gateways: `-us` for the Americas and `-eu` for Europe. The port is fixed per plan. | Plan | Americas gateway | Europe gateway | Port | | --- | --- | --- | --- | | Solve | `solve-us.netdash.io` | `solve-eu.netdash.io` | 1000 | | Scale | `scale-us.netdash.io` | `scale-eu.netdash.io` | 2000 | | Opus | `opus-us.netdash.io` | `opus-eu.netdash.io` | 3000 | | Mobile | `mobile-us.netdash.io` | `mobile-eu.netdash.io` | 4000 | Solve is our lowest-cost residential plan, Scale our mid-priced plan and Opus our highest-priced plan. They differ in which upstream residential pool they route to and in price. The syntax is identical, so you switch plans by changing the host and port. Mobile uses the same syntax with mobile exit IPs. Plans and prices are described on the [residential proxies](https://netdash.io/products/residential-proxies), [mobile proxies](https://netdash.io/products/mobile-proxies) and [pricing](https://netdash.io/pricing) pages. Your username and password work on every residential and mobile plan, but each plan has its own GB balance, and traffic through a plan's gateway counts against that plan's balance. The `-us` gateway runs in Ashburn and has lower latency for exits in the Americas; the `-eu` gateway runs in Frankfurt and has lower latency for exits in Europe, Africa and Asia. The examples on this site pair `US` with `scale-us.netdash.io` and `DE` or `GB` with `scale-eu.netdash.io`. For an exit country in Oceania, [ask support](mailto:support@netdash.io?subject=Support%20request) which gateway to use. Always include the port. curl, for example, assumes port 1080 when a proxy string has none, which doesn't match any netdash gateway. Eligible Opus accounts can enable two additional pools, shown in the dashboard as Opus Extended and HighSpeed. They have their own country lists and longer default sticky sessions, and their usernames are generated in the dashboard, so this page doesn't document a syntax for them. ## Username syntax The username is your base username followed by parameters, in this order, each starting with a hyphen: | Part | Required | Values | Example | |---|---|---|---| | Base username | Yes | Your username from the dashboard | `USERNAME` | | `-cc-{CC}` | Yes, in every username | ISO 3166-1 alpha-2 country code in uppercase. Use `GB` for the United Kingdom, not `UK`. | `-cc-US` | | `-session-{SID}` | For sticky sessions | Letters and digits only, no hyphens. The dashboard generates 8 lowercase characters. | `-session-a1b2c3d4` | | `-ttl-{MIN}` | Whenever `-session-` is used | Session lifetime in minutes. Dashboard presets: `15`, `30`, `720` (12 hours) and `10080` (7 days). | `-ttl-30` | Leave the session part out for a rotating session, and the gateway assigns exit IPs. Add it for a sticky session: requests with the same session ID keep the same exit IP for the TTL while that IP stays available. | Session | Username | |---|---| | Rotating, US exit | `USERNAME-cc-US` | | Rotating, UK exit | `USERNAME-cc-GB` | | Sticky, German exit, 30 minutes | `USERNAME-cc-DE-session-a1b2c3d4-ttl-30` | Parameters other than `-cc-`, `-session-` and `-ttl-` aren't part of the syntax. [Sessions](https://netdash.io/docs/sessions) and [country targeting](https://netdash.io/docs/targeting) explain both parts in detail. ## Authentication netdash authenticates every request with your username and password, and with nothing else: there is no IP allowlisting and no public account API. Usage, balances and credentials live in the dashboard, and no SDK is needed because netdash is a standard HTTP proxy. In the URL form, the username and password come before the `@`. If a value ever contains a character other than letters, digits and hyphens, such as `@`, `:` or `/`, percent-encode it (`@` becomes `%40`) or pass the username and password as separate fields. ## Output formats The dashboard generator can export your proxies in three formats. All three carry the same host, port, username and password: | Format | Example (Scale, Americas gateway, sticky US session) | Use it with | |---|---|---| | `http://user:pass@host:port` | `http://USERNAME-cc-US-session-a1b2c3d4-ttl-30:PASSWORD@scale-us.netdash.io:2000` | HTTP clients that take a proxy URL: curl, Python requests and httpx, Go `net/http`, Node.js undici `ProxyAgent` | | `host:port:user:pass` | `scale-us.netdash.io:2000:USERNAME-cc-US-session-a1b2c3d4-ttl-30:PASSWORD` | Tools and scripts that import proxy lists, one proxy per line | | `user:pass:host:port` | `USERNAME-cc-US-session-a1b2c3d4-ttl-30:PASSWORD:scale-us.netdash.io:2000` | Tools that expect the credentials before the host in a list | Browser automation tools take the server and the credentials as separate settings. Playwright's `proxy` option, for example, has `server` (`http://scale-us.netdash.io:2000`), `username` and `password` fields. The [Playwright](https://netdash.io/developers/playwright), [Puppeteer](https://netdash.io/developers/puppeteer) and [Selenium](https://netdash.io/developers/selenium) guides show the setup for each tool. ## Proxy string format examples The same three requests in curl, Python, Node.js and Go. Each prints the exit IP. ### Rotating, United States cURL: ```bash curl --max-time 30 -x "http://USERNAME-cc-US:PASSWORD@scale-us.netdash.io:2000" \ "https://api.ipify.org?format=json" ``` Python: ```python import requests proxy = "http://USERNAME-cc-US:PASSWORD@scale-us.netdash.io:2000" proxies = {"http": proxy, "https": proxy} # set both, or HTTPS requests bypass the proxy response = requests.get("https://api.ipify.org?format=json", proxies=proxies, timeout=30) response.raise_for_status() print(response.text) ``` Node.js: ```javascript import { ProxyAgent, fetch } from "undici"; const dispatcher = new ProxyAgent({ uri: "http://scale-us.netdash.io:2000", token: "Basic " + Buffer.from("USERNAME-cc-US:PASSWORD").toString("base64"), }); const res = await fetch("https://api.ipify.org?format=json", { dispatcher, signal: AbortSignal.timeout(30_000) }); console.log(await res.text()); ``` Go: ```go package main import ( "fmt" "io" "net/http" "net/url" "time" ) func main() { proxyURL, err := url.Parse("http://USERNAME-cc-US:PASSWORD@scale-us.netdash.io:2000") if err != nil { panic(err) } client := &http.Client{ Transport: &http.Transport{Proxy: http.ProxyURL(proxyURL)}, Timeout: 30 * time.Second, } resp, err := client.Get("https://api.ipify.org?format=json") if err != nil { panic(err) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { panic(err) } fmt.Println(string(body)) } ``` ### Sticky session, Germany cURL: ```bash curl --max-time 30 -x "http://USERNAME-cc-DE-session-a1b2c3d4-ttl-30:PASSWORD@scale-eu.netdash.io:2000" \ "https://api.ipify.org?format=json" ``` Python: ```python import requests proxy = "http://USERNAME-cc-DE-session-a1b2c3d4-ttl-30:PASSWORD@scale-eu.netdash.io:2000" proxies = {"http": proxy, "https": proxy} # set both, or HTTPS requests bypass the proxy response = requests.get("https://api.ipify.org?format=json", proxies=proxies, timeout=30) response.raise_for_status() print(response.text) ``` Node.js: ```javascript import { ProxyAgent, fetch } from "undici"; const dispatcher = new ProxyAgent({ uri: "http://scale-eu.netdash.io:2000", token: "Basic " + Buffer.from("USERNAME-cc-DE-session-a1b2c3d4-ttl-30:PASSWORD").toString("base64"), }); const res = await fetch("https://api.ipify.org?format=json", { dispatcher, signal: AbortSignal.timeout(30_000) }); console.log(await res.text()); ``` Go: ```go package main import ( "fmt" "io" "net/http" "net/url" "time" ) func main() { proxyURL, err := url.Parse("http://USERNAME-cc-DE-session-a1b2c3d4-ttl-30:PASSWORD@scale-eu.netdash.io:2000") if err != nil { panic(err) } client := &http.Client{ Transport: &http.Transport{Proxy: http.ProxyURL(proxyURL)}, Timeout: 30 * time.Second, } resp, err := client.Get("https://api.ipify.org?format=json") if err != nil { panic(err) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { panic(err) } fmt.Println(string(body)) } ``` ### Mobile, United Kingdom cURL: ```bash curl --max-time 30 -x "http://USERNAME-cc-GB:PASSWORD@mobile-eu.netdash.io:4000" \ "https://api.ipify.org?format=json" ``` Python: ```python import requests proxy = "http://USERNAME-cc-GB:PASSWORD@mobile-eu.netdash.io:4000" proxies = {"http": proxy, "https": proxy} # set both, or HTTPS requests bypass the proxy response = requests.get("https://api.ipify.org?format=json", proxies=proxies, timeout=30) response.raise_for_status() print(response.text) ``` Node.js: ```javascript import { ProxyAgent, fetch } from "undici"; const dispatcher = new ProxyAgent({ uri: "http://mobile-eu.netdash.io:4000", token: "Basic " + Buffer.from("USERNAME-cc-GB:PASSWORD").toString("base64"), }); const res = await fetch("https://api.ipify.org?format=json", { dispatcher, signal: AbortSignal.timeout(30_000) }); console.log(await res.text()); ``` Go: ```go package main import ( "fmt" "io" "net/http" "net/url" "time" ) func main() { proxyURL, err := url.Parse("http://USERNAME-cc-GB:PASSWORD@mobile-eu.netdash.io:4000") if err != nil { panic(err) } client := &http.Client{ Transport: &http.Transport{Proxy: http.ProxyURL(proxyURL)}, Timeout: 30 * time.Second, } resp, err := client.Get("https://api.ipify.org?format=json") if err != nil { panic(err) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { panic(err) } fmt.Println(string(body)) } ``` _Checked against a local test proxy._ ### Build your own string Pick a plan, gateway, country and session type, and copy the string or the code. Pair the gateway with the exit country: `-us` for the Americas, `-eu` for Europe. For any other country, [ask support](mailto:support@netdash.io?subject=Support%20request) which gateway to use. cURL: ```bash curl --max-time 30 -x "http://USERNAME-cc-US:PASSWORD@scale-us.netdash.io:2000" \ "https://api.ipify.org?format=json" ``` Python: ```python import requests proxy = "http://USERNAME-cc-US:PASSWORD@scale-us.netdash.io:2000" proxies = {"http": proxy, "https": proxy} # set both, or HTTPS requests bypass the proxy response = requests.get("https://api.ipify.org?format=json", proxies=proxies, timeout=30) response.raise_for_status() print(response.text) ``` Node.js: ```javascript import { ProxyAgent, fetch } from "undici"; const dispatcher = new ProxyAgent({ uri: "http://scale-us.netdash.io:2000", token: "Basic " + Buffer.from("USERNAME-cc-US:PASSWORD").toString("base64"), }); const res = await fetch("https://api.ipify.org?format=json", { dispatcher, signal: AbortSignal.timeout(30_000) }); console.log(await res.text()); ``` Go: ```go package main import ( "fmt" "io" "net/http" "net/url" "time" ) func main() { proxyURL, err := url.Parse("http://USERNAME-cc-US:PASSWORD@scale-us.netdash.io:2000") if err != nil { panic(err) } client := &http.Client{ Transport: &http.Transport{Proxy: http.ProxyURL(proxyURL)}, Timeout: 30 * time.Second, } resp, err := client.Get("https://api.ipify.org?format=json") if err != nil { panic(err) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { panic(err) } fmt.Println(string(body)) } ``` ## Protocol netdash is an HTTP proxy. HTTPS websites go through a CONNECT tunnel: your client asks the gateway to open a tunnel to the website and then runs TLS inside it, so your client checks the website's own certificate as usual. Use the `http://` scheme for the proxy URL, also for HTTPS websites. With `https://` as the proxy scheme, the client tries to start TLS with the gateway itself, and the connection fails. ## ISP and datacenter lists Static ISP and datacenter IPs don't use the username parameters above. Each order is delivered as a static list in `host:port:user:pass` format, with its own username and password: ISP or datacenter list: ```text 203.0.113.10:PORT:user:pass ``` The location is chosen when you order, and each IP stays assigned to you for as long as the subscription renews. > **Checkout paused** Self-serve checkout for ISP and datacenter proxies is paused. Contact sales about [ISP](mailto:sales@netdash.io?subject=ISP%20proxies%20inquiry&body=Locations%3A%0ANumber%20of%20IPs%3A%0AUse%20case%3A%0A) or [datacenter](mailto:sales@netdash.io?subject=Datacenter%20proxies%20inquiry&body=Locations%3A%0ANumber%20of%20IPs%3A%0AUse%20case%3A%0A) availability. ## Guides by language and tool - [Python](https://netdash.io/developers/python): requests and httpx, with both proxy keys and timeouts. - [Node.js](https://netdash.io/developers/nodejs): undici ProxyAgent with fetch, and axios with https-proxy-agent. - [Go](https://netdash.io/developers/go): An authenticated proxy URL in a net/http Transport. - [Scrapy](https://netdash.io/developers/scrapy): Proxies through request meta or a downloader middleware. - [Selenium](https://netdash.io/developers/selenium): Authenticated proxies for WebDriver sessions through a small local forwarder. - [Playwright](https://netdash.io/developers/playwright): Proxies per browser or per context, in Node.js and Python. - [Puppeteer](https://netdash.io/developers/puppeteer): --proxy-server with page.authenticate, and per-context proxies. - [Claude Code](https://netdash.io/developers/claude-code): Credentials in .env and a proxy per client in agent-built projects. New to netdash? Start with the [quickstart](https://netdash.io/docs/quickstart). Stuck? See [troubleshooting](https://netdash.io/docs/troubleshooting) or [email support](mailto:support@netdash.io?subject=Support%20request). ## Sources - [curl man page: -x, --proxy](https://curl.se/docs/manpage.html) (accessed 2026-10-09) - [Requests documentation: Advanced Usage, Proxies](https://requests.readthedocs.io/en/latest/user/advanced/#proxies) (accessed 2026-10-09) - [HTTPX documentation: Proxies](https://www.python-httpx.org/advanced/proxies/) (accessed 2026-10-09) - [Go net/http package: ProxyURL](https://pkg.go.dev/net/http#ProxyURL) (accessed 2026-10-09) - [undici documentation: ProxyAgent](https://github.com/nodejs/undici/blob/main/docs/docs/api/ProxyAgent.md) (accessed 2026-10-09) - [Playwright documentation: Network, HTTP Proxy](https://playwright.dev/docs/network#http-proxy) (accessed 2026-10-09) --- # Rotating and sticky sessions > Leave the session part out and the gateway assigns exit IPs (rotating). Add -session-{SID}-ttl-{MIN} and requests with the same session ID keep the same exit IP for that many minutes while it stays available (sticky). Source: https://netdash.io/docs/sessions · Updated 2026-10-09 You choose the session mode per request, in the username. A sticky session proxy username pins the exit IP for a lifetime you set, while that IP stays available; a rotating one doesn't pin it. The gateway, the port and the password stay the same in both modes. | Mode | Username | Exit IP | |---|---|---| | Rotating | `USERNAME-cc-US` | Assigned by the gateway, not pinned | | Sticky | `USERNAME-cc-US-session-a1b2c3d4-ttl-30` | The same for 30 minutes while it stays available | ## Rotating sessions A username without session parameters, such as `USERNAME-cc-US`, is a rotating session: the gateway assigns exit IPs, and the exit IP isn't pinned to your requests. Requests sent through one HTTPS tunnel share that tunnel's exit IP. If consecutive requests must share an exit IP, use a sticky session instead of relying on how your client reuses connections. Rotating sessions suit independent requests, where it doesn't matter whether two requests leave from the same IP: separate page fetches, scheduled checks of public pages, or one-off lookups. ## Sticky session proxy syntax Add `-session-` with a session ID and `-ttl-` with a lifetime in minutes after the country code: Sticky username: ```text USERNAME-cc-{CC}-session-{SID}-ttl-{MIN} USERNAME-cc-US-session-a1b2c3d4-ttl-30 ``` Requests that use the same session ID keep the same exit IP for the TTL, while that IP stays available. Always send `-session-` and `-ttl-` together. ## Session IDs - Use letters and digits only, no hyphens. The dashboard generates 8 lowercase characters, such as `a1b2c3d4`. - Reuse an ID to stay in the same session. Use a new ID to start a new session. - Give each task, worker or agent run its own ID. Workers that share an ID share one session, and with it one exit IP. - Generate IDs in code: in Python, `secrets.token_hex(4)` returns 8 characters from `0-9` and `a-f`; in Node.js, `randomBytes(4).toString("hex")` from `node:crypto` does the same. ## Session lifetime (TTL) `-ttl-` sets how long the session keeps its exit IP, in minutes. The dashboard offers these presets: | Dashboard preset | Username suffix | | --- | --- | | 15 minutes | `-session-a1b2c3d4-ttl-15` | | 30 minutes | `-session-a1b2c3d4-ttl-30` | | 12 hours | `-session-a1b2c3d4-ttl-720` | | 7 days | `-session-a1b2c3d4-ttl-10080` | The dashboard offers sticky lifetimes from 15 minutes up to 7 days. Include `-ttl-` every time you use `-session-`, and pick the shortest lifetime that covers the task. When the lifetime ends, don't count on the same exit IP: start the next task with a new session ID. ## When the exit IP changes early A sticky session keeps its exit IP only while that IP stays available. Residential and mobile exit IPs can become unavailable before the TTL ends, and then the next request in the session may leave from a different IP. If your task depends on one IP, detect the change instead of assuming it can't happen: 1. Record the exit IP at the start of the task, for example from `https://api.ipify.org?format=json` through the same session. 2. Check it again before steps that depend on it, or watch for signs that the website no longer recognizes your session, such as a multi-page form starting over. 3. If the IP changed, restart the step with a new session ID rather than continuing halfway on a different IP. ## Choosing rotating or sticky | | Mode | Lifetime | | --- | --- | --- | | Independent page fetches | Rotating | No session parameters | | A multi-step flow on one website | Sticky | 15 or 30 minutes (`-ttl-15`, `-ttl-30`) | | An AI agent task | Sticky, one session ID per run | A preset that covers the whole run | | A long QA or testing session | Sticky | 12 hours (`-ttl-720`) | Browser automation often runs multi-step flows across several pages, which is what sticky sessions are for. See [browser automation](https://netdash.io/solutions/browser-automation) and the [Playwright guide](https://netdash.io/developers/playwright) for complete setups. ## Sessions across plans The session syntax is the same on Solve, Scale, the Opus plan and Mobile, so code that builds sticky usernames works on every residential and mobile plan. The Opus Extended and HighSpeed pools on eligible Opus accounts use usernames generated in the dashboard and have longer default sticky sessions. Static ISP and datacenter IPs take no session parameters: each IP stays assigned to you for as long as the subscription renews. ## Examples Sticky sessions with curl in the US and Germany, and a Python script that generates its own session ID and prints the exit IP of two requests in that session: cURL (US): ```bash curl --max-time 30 -x "http://USERNAME-cc-US-session-a1b2c3d4-ttl-30:PASSWORD@scale-us.netdash.io:2000" \ "https://api.ipify.org?format=json" ``` cURL (Germany): ```bash curl --max-time 30 -x "http://USERNAME-cc-DE-session-a1b2c3d4-ttl-30:PASSWORD@scale-eu.netdash.io:2000" \ "https://api.ipify.org?format=json" ``` Python: ```python import os import secrets from typing import Optional import requests GATEWAY = "scale-eu.netdash.io:2000" # Scale plan, Europe gateway def proxy_url(country: str, session_id: Optional[str] = None, ttl: int = 30) -> str: username = f"{os.environ['NETDASH_USERNAME']}-cc-{country}" if session_id: username += f"-session-{session_id}-ttl-{ttl}" return f"http://{username}:{os.environ['NETDASH_PASSWORD']}@{GATEWAY}" session_id = secrets.token_hex(4) # 8 characters, letters and digits only proxy = proxy_url("DE", session_id) proxies = {"http": proxy, "https": proxy} # set both, or HTTPS requests skip the proxy for _ in range(2): response = requests.get("https://api.ipify.org?format=json", proxies=proxies, timeout=30) response.raise_for_status() print(session_id, response.json()["ip"]) ``` _Checked against a local test proxy._ The Python script reads `NETDASH_USERNAME` and `NETDASH_PASSWORD` from the environment. Load them from a `.env` file or your secret manager, and never commit them to source control. The [proxy format reference](https://netdash.io/docs/proxy-format) has sticky session examples for Node.js and Go. ## Related - [Static vs rotating proxies](https://netdash.io/blog/static-vs-rotating-proxies): Which workloads need a stable IP and which don't. - [Playwright guide](https://netdash.io/developers/playwright): Pin a sticky session per browser context for multi-step flows. - [Proxies for AI agents](https://netdash.io/solutions/ai-agents): Give an agent an exit country and one session per run. Choosing the exit country works the same way in both modes: see [country targeting](https://netdash.io/docs/targeting). Stuck? See [troubleshooting](https://netdash.io/docs/troubleshooting) or [email support](mailto:support@netdash.io?subject=Support%20request). ## Sources - [Python documentation: secrets.token_hex](https://docs.python.org/3/library/secrets.html#secrets.token_hex) (accessed 2026-10-09) - [Node.js documentation: crypto.randomBytes](https://nodejs.org/api/crypto.html#cryptorandombytessize-callback) (accessed 2026-10-09) - [Requests documentation: Advanced Usage, Proxies](https://requests.readthedocs.io/en/latest/user/advanced/#proxies) (accessed 2026-10-09) --- # Country targeting > Choose the exit country with -cc- and an ISO 3166-1 alpha-2 code in the username, for example USERNAME-cc-DE. Targeting is country-level. Source: https://netdash.io/docs/targeting · Updated 2026-10-09 Proxy country targeting on netdash is part of the username: `-cc-` followed by a two-letter country code sets the country your requests exit from. The same parameter works for rotating and sticky sessions, on every residential plan and on Mobile. ## Proxy country targeting with the `-cc-` parameter Every username includes `-cc-` and a country code, also for rotating sessions. These three requests exit in the United States, Germany and the United Kingdom: US, Germany and United Kingdom: ```bash curl --max-time 30 -x "http://USERNAME-cc-US:PASSWORD@scale-us.netdash.io:2000" "https://api.ipify.org?format=json" curl --max-time 30 -x "http://USERNAME-cc-DE:PASSWORD@scale-eu.netdash.io:2000" "https://api.ipify.org?format=json" curl --max-time 30 -x "http://USERNAME-cc-GB:PASSWORD@scale-eu.netdash.io:2000" "https://api.ipify.org?format=json" ``` _Checked against a local test proxy._ In a sticky session, the country code comes first and the session parameters follow: `USERNAME-cc-DE-session-a1b2c3d4-ttl-30`. Changing the country means changing the username, so start a new session ID when you switch countries in a sticky flow. ## Country codes Write the country as an ISO 3166-1 alpha-2 code in uppercase: `US` for the United States, `DE` for Germany. **Use GB, not UK.** The ISO code for the United Kingdom is `GB`, and that is what netdash expects in `-cc-GB`. The dashboard generator converts `UK` to `GB` for you; in your own code, do the same before you build the username. Codes for other countries are listed in the [ISO 3166 country codes](https://www.iso.org/iso-3166-country-codes.html) standard. ## Choose the gateway Each plan has a gateway for the Americas (`-us`) and one for Europe (`-eu`), on the same port. Use `-us` for exits in the Americas and `-eu` for exits in Europe: | Exit country | Code | Gateway (Scale plan) | |---|---|---| | United States | `US` | `scale-us.netdash.io:2000` | | Germany | `DE` | `scale-eu.netdash.io:2000` | | United Kingdom | `GB` | `scale-eu.netdash.io:2000` | For exits in Africa and Asia, the `-eu` gateway has lower latency; for an exit country in Oceania, ask support which gateway to use. The [proxy format reference](https://netdash.io/docs/proxy-format#endpoints) lists the hosts and ports of every plan. ## Which countries are available Pick an exit country with its ISO code. Contact support to confirm availability for a specific country, especially before you plan a project around it. > **Before you rely on a country** Send a test request with that country code and check the exit IP, or [ask support](mailto:support@netdash.io?subject=Support%20request) to confirm availability for your plan. The proxy locations page shows the two gateway regions and the fixed locations of the static ISP and datacenter IPs. [Proxy locations](https://netdash.io/locations) ## Mobile, ISP and datacenter Mobile proxies use the same `-cc-` parameter on the mobile gateway: Mobile, United Kingdom: ```bash curl --max-time 30 -x "http://USERNAME-cc-GB:PASSWORD@mobile-eu.netdash.io:4000" "https://api.ipify.org?format=json" ``` _Checked against a local test proxy._ On mobile, the country is the only thing you choose: carrier, city and network generation can't be selected. Static ISP and datacenter IPs take no `-cc-` parameter. You choose the location when you order, and each IP stays assigned to you for as long as the subscription renews. ISP IPs are in Ashburn (United States), Frankfurt (Germany), London (United Kingdom) and Sydney (Australia); datacenter IPs are in the United States, Germany, the United Kingdom and the Netherlands. Self-serve checkout for both is paused, so contact sales about [ISP](mailto:sales@netdash.io?subject=ISP%20proxies%20inquiry&body=Locations%3A%0ANumber%20of%20IPs%3A%0AUse%20case%3A%0A) or [datacenter](mailto:sales@netdash.io?subject=Datacenter%20proxies%20inquiry&body=Locations%3A%0ANumber%20of%20IPs%3A%0AUse%20case%3A%0A) availability. ## What isn't supported Targeting is country-level. You can't select a city, a state or region, a ZIP code, an ASN or a carrier, and parameters other than `-cc-`, `-session-` and `-ttl-` aren't part of the username syntax. If your work depends on a specific city or network, test whether country-level exits give you what you need before you buy a larger amount. ## Check the exit country Request `https://api.ipify.org?format=json` through the proxy to see the exit IP, then look that IP up in the geolocation source that matters to you, ideally the one the website itself uses. Geolocation databases can disagree about the country of an IP, so two services may place the same exit IP differently. When a website shows content for the wrong country, also check that your client doesn't send language or location hints of its own, such as a stored cookie or an `Accept-Language` header from a different locale. For workflows built on the exit country, see [localization and QA testing](https://netdash.io/solutions/localization-testing) and [SEO monitoring](https://netdash.io/solutions/seo-monitoring). ## Related - [Proxy format reference](https://netdash.io/docs/proxy-format): Endpoints, the full username syntax and output formats. - [Sessions](https://netdash.io/docs/sessions): Combine a country with a sticky session and a lifetime. - [Proxy locations](https://netdash.io/locations): Gateway regions and the locations of static ISP and datacenter IPs. Stuck? See [troubleshooting](https://netdash.io/docs/troubleshooting) or [email support](mailto:support@netdash.io?subject=Support%20request). ## Sources - [ISO: ISO 3166 country codes](https://www.iso.org/iso-3166-country-codes.html) (accessed 2026-10-09) - [ipify API](https://www.ipify.org/) (accessed 2026-10-09) --- # Troubleshooting proxy errors > Most problems come from four things: credentials, a host and port that don't match the plan, a client that doesn't apply the proxy to HTTPS, or an outbound firewall. Start with the quick checks. Source: https://netdash.io/docs/troubleshooting · Updated 2026-10-09 This page covers the errors you can run into when you connect to netdash, each under the message your client prints, with its causes and the fix. Start with a proxy 407 error if you see one: it means the gateway didn't accept the username or password, and nothing else works until that is fixed. ## Quick checks Run through this list before you dig into a specific error: - The host and port belong to the plan you bought. Compare them with the [endpoint table](https://netdash.io/docs/proxy-format#endpoints). - The proxy URL starts with `http://`, not `https://`, also for HTTPS websites. - The username contains `-cc-` and an uppercase country code, for example `USERNAME-cc-US`. - The United Kingdom is `GB`, not `UK`. - If you use `-session-`, the username also has `-ttl-`, and the session ID has only letters and digits. - The plan you're sending traffic to has GB left in the dashboard. - The URL you request is in quotes when you run curl in a shell. - Your network allows outbound connections to the gateway's port. To test outside your application, run one request with curl. If it prints an IP address, the gateway and your credentials work, and the problem is in your client's configuration: Test request: ```bash curl --max-time 30 -x "http://USERNAME-cc-US:PASSWORD@scale-us.netdash.io:2000" \ "https://api.ipify.org?format=json" ``` _Checked against a local test proxy._ ## 407 Proxy Authentication Required: fixing a proxy 407 error The gateway answers `407 Proxy Authentication Required` when it doesn't accept the credentials it received. How the error looks depends on the client: | Client | Message | |---|---| | curl | `curl: (56) CONNECT tunnel failed, response 407` (wording varies by version) | | Python requests | `ProxyError: ... Tunnel connection failed: 407 Proxy Authentication Required` | | Other HTTP clients | A `407` status, or a proxy or tunnel error that mentions 407 | ### Why the gateway returns 407 - **Wrong password.** Copy it again from the dashboard. When you paste it into a `.env` file, check for spaces or line breaks around it. - **Mistyped base username.** The part before `-cc-` must match your username in the dashboard exactly. - **Usernames outside the documented syntax.** A missing `-cc-`, `UK` instead of `GB`, `-session-` without `-ttl-`, or a hyphen inside the session ID aren't supported. Correct them to the [username syntax](https://netdash.io/docs/proxy-format#username-syntax) before you look further. - **Credentials that never reach the gateway.** Browser automation tools take the proxy server and the credentials as separate settings. Playwright's `proxy` option, for example, has `server`, `username` and `password` fields. With Puppeteer the authentication step is easy to miss, and Selenium with Chrome needs a local forwarder: see the [Puppeteer](https://netdash.io/developers/puppeteer) and [Selenium](https://netdash.io/developers/selenium) guides. - **Unencoded special characters.** If a value contains `@`, `:` or `/`, percent-encode it in the proxy URL. ### Find the cause with curl -v Send one request with curl's verbose flag and read the gateway's answer: Verbose curl: ```bash curl -v --max-time 30 -x "http://USERNAME-cc-US:PASSWORD@scale-us.netdash.io:2000" "https://api.ipify.org?format=json" ``` _Checked against a local test proxy._ In verbose output, lines starting with `>` are what curl sent, lines starting with `<` are what it received, and lines starting with `*` are curl's own notes. For an HTTPS website, curl first sends `> CONNECT api.ipify.org:443 HTTP/1.1` to the gateway. The lines starting with `<` right after that block are the gateway's answer to the CONNECT request: - `< HTTP/1.1 407 ...` followed by `* CONNECT tunnel failed, response 407`: the gateway rejected the username or password. Fix them using the causes above. - `< HTTP/1.1 200 ...` followed by `* CONNECT tunnel established, response 200`: the gateway accepted your credentials. Anything that fails after this point happens inside the tunnel, between your client and the website. If curl works and your application doesn't, compare the exact username your application sends with the one in the curl command. > **Before you share verbose output** Verbose output contains a `Proxy-Authorization` line that encodes your username and password. Delete that line before you paste the output into a ticket, a chat or an issue. ## ERR_TUNNEL_CONNECTION_FAILED in a browser Chromium-based browsers, including Chromium driven by Playwright, Puppeteer or Selenium, report `net::ERR_TUNNEL_CONNECTION_FAILED` when a tunnel through the proxy couldn't be established. With netdash, start with the credentials: 1. Run the same username and password through curl, as in the [407 section](#407-proxy-authentication-required-fixing-a-proxy-407-error). 2. If curl prints an IP and the browser fails, the browser is most likely not receiving the credentials. Pass them through Playwright's or Puppeteer's authentication setting, not inside the `--proxy-server` value; Selenium with Chrome has no such setting, so use a local forwarder. The [Playwright](https://netdash.io/developers/playwright), [Puppeteer](https://netdash.io/developers/puppeteer) and [Selenium](https://netdash.io/developers/selenium) guides show how. 3. If curl fails too, work through the 407 causes above. `net::ERR_PROXY_CONNECTION_FAILED` is different: the browser couldn't connect to the proxy server at all. Treat it like a timeout, below. ## Requests skip the proxy The request works, but the IP it prints isn't a netdash exit IP: it's your own address, or the address of another proxy set in your environment. The client didn't send the request through netdash: | Client | Cause | Fix | |---|---|---| | Python requests | Only the `http` key is set in `proxies`. requests chooses the proxy by the scheme of the website's URL, so HTTPS requests skip the proxy. | Set both `http` and `https` to the same `http://` proxy URL. See the [Python guide](https://netdash.io/developers/python). | | Python requests with a `Session` | Proxy variables in the environment, such as `HTTPS_PROXY`, overwrite values you set in `session.proxies`. | Pass `proxies=` on each request, or remove the variables from the environment. | | Node.js built-in `fetch` | `fetch` reads `HTTP_PROXY` and `HTTPS_PROXY` only when proxy support from the environment is enabled: `NODE_USE_ENV_PROXY=1` (Node.js 22.21.0 or later, or 24.0.0 or later) or the `--use-env-proxy` flag (22.21.0, or 24.5.0 and later). | Use `ProxyAgent` and `fetch` from the `undici` package. See the [Node.js guide](https://netdash.io/developers/nodejs). | | Any client that reads `NO_PROXY` | Hosts listed in `NO_PROXY` or `no_proxy` bypass the proxy. | Remove the website's host from that variable. | Setting `HTTPS_PROXY` for the whole process is not a good fix either. A global `HTTPS_PROXY` applies to every request the process makes, including calls to model APIs and package registries. Those requests would go through netdash and count against your balance. Set the proxy on the client that fetches websites instead. ## Timeouts and connection refused Typical messages: `curl: (28) Connection timed out after ...`, `curl: (7) Failed to connect to scale-us.netdash.io port 2000`, `curl: (5) Could not resolve proxy: ...`, or a client-side `ConnectTimeout`. ### Why connections time out - **An outbound firewall.** The gateways listen on ports 1000, 2000, 3000 and 4000. Corporate networks, some CI runners and some hosting environments restrict outbound ports. If `curl -v` stops after the `* Trying ...` line, the connection to the gateway isn't getting through. - **A host or port typo.** `Could not resolve proxy` means the gateway host name is wrong. Copy host and port from the [endpoint table](https://netdash.io/docs/proxy-format#endpoints). - **No timeout, or a very short one.** requests, for example, doesn't time out unless you set a timeout. The examples on this site use 30 seconds. - **A sticky exit IP became unavailable.** Requests in that session may stall or fail. Start a new session ID; see [sessions](https://netdash.io/docs/sessions#when-the-exit-ip-changes-early). ### Rule out a firewall Test from a different network, for example your laptop instead of a CI runner, to rule out a firewall. If the same curl command works there, ask the network's administrator to allow outbound connections to the gateway host and port you use. ## TLS and certificate errors Typical messages: `ProxyError: Unable to connect to proxy. Your proxy appears to only use HTTP and not HTTPS` with `SSLError ... WRONG_VERSION_NUMBER` in Python, `curl: (35) ...` with a TLS error, or `CERTIFICATE_VERIFY_FAILED`. ### Why TLS fails - **`https://` as the proxy scheme.** The client then tries to start TLS with the gateway itself. netdash is an HTTP proxy, so this fails. In Python, also check `HTTPS_PROXY` and `ALL_PROXY` in the environment, which can carry an `https://` value without your code knowing. - **Certificate verification fails for the website.** netdash carries HTTPS in a CONNECT tunnel, so the certificate your client checks is the website's own. A failure points at your environment, such as an outdated CA bundle or security software on your machine that inspects HTTPS, or at the website. ### Fix it without disabling verification Use `http://` as the proxy scheme for every website. For certificate errors, update the CA certificates your client uses (for Python requests, upgrade `certifi`), then retry. Never disable TLS verification to make an error go away. `verify=False` in Python, `rejectUnauthorized: false` in Node.js or `NODE_TLS_REJECT_UNAUTHORIZED=0` hide the cause and let anyone between you and the website read and change the traffic. ## Wrong exit country The exit IP is in a different country from the one you asked for. - **Check the code.** It must be an uppercase ISO 3166-1 alpha-2 code: `GB` for the United Kingdom, not `UK`. With a misspelled country code, the country you meant never reaches the gateway; a username without `-cc-` isn't supported. - **Check the gateway.** Use `-us` for exits in the Americas and `-eu` for exits in Europe. For other countries, ask support which gateway to use. - **Check the lookup.** Geolocation databases can disagree about an IP's country. Look up the exit IP in the source the website uses, if you know it, before you conclude the exit is wrong. [Country targeting](https://netdash.io/docs/targeting) has the full rules and examples. ## A sticky session changed IP The exit IP changed although the username has a session ID. - **The exit IP became unavailable.** A sticky session keeps its IP only while that IP stays available. Start the step again with a new session ID. - **The lifetime ended.** After `-ttl-` minutes, don't count on the same IP. Choose a lifetime that covers the whole task. - **The session ID differs between requests.** A typo, an ID generated per request instead of per task, or a separate ID per worker when you expected one shared session. [Sessions](https://netdash.io/docs/sessions) explains how to detect a changed IP and restart cleanly. ## zsh: no matches found Unquoted URL in zsh: ```text % curl -x http://USERNAME-cc-US:PASSWORD@scale-us.netdash.io:2000 https://api.ipify.org?format=json zsh: no matches found: https://api.ipify.org?format=json ``` zsh treats `?` and `*` in an unquoted word as filename patterns and, by default, stops with an error when no file matches. Other shells cause related problems: an unquoted `&` ends the command in bash and runs it in the background, so every query parameter after it is lost. Put the proxy URL and the website URL in quotes: Quoted URLs: ```bash curl --max-time 30 -x "http://USERNAME-cc-US:PASSWORD@scale-us.netdash.io:2000" "https://api.ipify.org?format=json" ``` _Checked against a local test proxy._ Double quotes still let the shell expand `$`, and in interactive bash and zsh also `!`. If your password contains either character, use single quotes around the proxy URL instead. ## Requests fail on one plan Your username and password are the same on every residential and mobile plan, but each plan keeps its own GB balance. If requests fail on one plan but work on another, check that plan's balance on the Residential (or Mobile) page in the dashboard. You can add GB to a plan with a new order in the [shop](https://app.netdash.io/shop). ## The website returns 403, 429 or a challenge page A 403, 429 or challenge page that arrives through an established tunnel is the website's answer to your request: netdash delivered the request and passes the response back unchanged. In `curl -v` output, that response comes after `CONNECT tunnel established, response 200`. > **What netdash doesn't do** netdash forwards your traffic unchanged. It doesn't solve CAPTCHAs, render JavaScript or retry requests. Targeting is country-level. What to do depends on the answer: - **429 Too Many Requests:** you're sending more than the website accepts. Slow down, and if the response has a `Retry-After` header, wait at least that long before the next request. - **403 Forbidden or a challenge page:** stop. Check the website's terms and `robots.txt`, and use its official API if it has one. > **Responsible use** Respect robots.txt and each website's terms, keep request rates reasonable and back off when a site slows you down, don't collect personal data without a lawful basis, and don't access content behind logins or paywalls without authorization. netdash doesn't solve CAPTCHAs or bot challenges, and your code shouldn't try to get past them either. If a site rate-limits or challenges your requests, slow down, check its terms, or use its official API. Read [how we expect netdash to be used](https://netdash.io/responsible-use). ## Still stuck? Email support and include these details: - Plan and gateway `host:port` - The username you used, **without** the password - Time of the failure, in UTC - The website's domain - The full error message - Client and version, for example curl, Python requests or Playwright Leave your password out of every message. The button below opens an email with these fields already in place. [Email support](mailto:support@netdash.io?subject=Proxy%20troubleshooting&body=Plan%3A%0AGateway%20host%3Aport%3A%0AUsername%20%28no%20password%29%3A%0ATime%20%28UTC%29%3A%0AWebsite%20domain%3A%0AError%20message%3A%0AClient%20and%20version%3A%0A) [Ask in Discord](https://discord.gg/vrfAN7hFKx) We don't currently publish a live status page. [Service status](https://netdash.io/status) lists the first checks, and [contact](https://netdash.io/contact) has every way to reach us, including live chat on this site. ## Related - [Proxy format reference](https://netdash.io/docs/proxy-format): Endpoints, username syntax and output formats. - [Sessions](https://netdash.io/docs/sessions): Sticky lifetimes, session IDs and early IP changes. - [Country targeting](https://netdash.io/docs/targeting): Country codes, GB for the UK and gateway choice. ## Sources - [curl man page: -v, --verbose and -x, --proxy](https://curl.se/docs/manpage.html) (accessed 2026-10-09) - [Requests documentation: Advanced Usage (Proxies, Timeouts, SSL Cert Verification)](https://requests.readthedocs.io/en/latest/user/advanced/#proxies) (accessed 2026-10-09) - [urllib3 documentation: Your proxy appears to only use HTTP and not HTTPS](https://urllib3.readthedocs.io/en/latest/advanced-usage.html#https-proxy-error-http-proxy) (accessed 2026-10-09) - [Node.js Learn: Enterprise Network Configuration](https://nodejs.org/en/learn/http/enterprise-network-configuration) (accessed 2026-10-09) - [Chromium source: net/base/net_error_list.h](https://chromium.googlesource.com/chromium/src/+/HEAD/net/base/net_error_list.h) (accessed 2026-10-09) - [Playwright documentation: Network, HTTP Proxy](https://playwright.dev/docs/network#http-proxy) (accessed 2026-10-09) - [zsh manual: Options (NOMATCH)](https://zsh.sourceforge.io/Doc/Release/Options.html) (accessed 2026-10-09) - [zsh manual: Expansion (History Expansion)](https://zsh.sourceforge.io/Doc/Release/Expansion.html) (accessed 2026-10-09) - [Bash Reference Manual: Lists of Commands](https://www.gnu.org/software/bash/manual/html_node/Lists.html) (accessed 2026-10-09) - [Bash Reference Manual: Double Quotes](https://www.gnu.org/software/bash/manual/html_node/Double-Quotes.html) (accessed 2026-10-09) - [Bash Reference Manual: Single Quotes](https://www.gnu.org/software/bash/manual/html_node/Single-Quotes.html) (accessed 2026-10-09) --- # Billing and balances > Residential and mobile GB are one-time purchases with graduated pricing per order. Each plan keeps its own balance, and purchased GB never expire. Source: https://netdash.io/docs/billing · Updated 2026-10-09 netdash bills residential and mobile proxies by the GB and static ISP and datacenter IPs by the IP. Balances, order pricing and renewals are explained below; the rates themselves are listed on the [pricing](https://netdash.io/pricing) page. ## How netdash billing works netdash billing for residential and mobile proxies works per order. You buy GB for one plan in a one-time order of 1 to 50,000 GB, priced in USD and paid by card through Stripe Checkout. There is no subscription for residential or mobile plans: nothing renews, and you place a new order when you need more GB. The plan is provisioned automatically once payment is confirmed. [Open the shop](https://app.netdash.io/shop) [See pricing](https://netdash.io/pricing) ## One balance per plan netdash has three residential plans, Solve (our lowest-cost residential plan), Scale and the Opus plan, plus Mobile. Your username and password work on all four, but each plan has its own GB balance: - GB bought for Scale don't top up Solve, and the other way round. - Mobile has a balance of its own. - Traffic through a plan's gateway counts against that plan's balance, so the host and port you use decide which balance is charged. The [proxy format reference](https://netdash.io/docs/proxy-format#endpoints) lists the gateway of each plan. Buy GB for the plan you actually send traffic through. If you switch plans, the GB on the old plan stay there for later. ## Graduated pricing per order Each GB in an order is charged at the rate of the band it falls in. The bands are ranges of GB within one order: GB 1–100 are charged at the first band's rate, GB 101–500 at the second band's rate, and so on through eight bands, the last covering GB 25,001–50,000. The band subtotals add up to the order total, which you pay once. For example, an order of 300 GB is charged as 100 GB at the first band's rate plus 200 GB at the second band's rate. It is not a flat discount on the whole order. Bands apply within one order; separate orders are priced separately. Every new order starts again in the first band: an order is never priced together with earlier ones. Every band rate for every plan, and a calculator for any order up to 50,000 GB, are on the [residential proxy pricing](https://netdash.io/pricing#calculator) page. ## Purchased GB never expire Purchased GB never expire. Unused GB stay on the plan's balance until you use them, so you can buy for a project now and keep whatever is left for the next one. ## How usage is counted You pay for the traffic you use. There is no hidden multiplier on metered GB. ## Check your balance The remaining GB of each plan are shown on the Residential (or Mobile) page in the dashboard. If requests fail on one plan but work on another, check that plan's balance first; [troubleshooting](https://netdash.io/docs/troubleshooting#requests-fail-on-one-plan) covers this case. ## ISP and datacenter billing Static ISP and datacenter proxies are billed differently from residential and mobile: | | Residential and mobile | ISP and datacenter | |---|---|---| | Billed by | GB of traffic | IP, with no per-GB charge | | Payment | One-time order | Renews every 30 days | | Stopping | Nothing to cancel | Cancel in the billing portal before the renewal | | What you keep | Unused GB, which never expire | Each IP, for as long as the subscription renews | Per-IP prices for [ISP proxies](https://netdash.io/products/isp-proxies) and [datacenter proxies](https://netdash.io/products/datacenter-proxies) are listed under [ISP pricing](https://netdash.io/pricing#isp) and [datacenter pricing](https://netdash.io/pricing#datacenter). > **Checkout paused** Self-serve checkout for ISP and datacenter proxies is paused. Contact sales about [ISP](mailto:sales@netdash.io?subject=ISP%20proxies%20inquiry&body=Locations%3A%0ANumber%20of%20IPs%3A%0AUse%20case%3A%0A) or [datacenter](mailto:sales@netdash.io?subject=Datacenter%20proxies%20inquiry&body=Locations%3A%0ANumber%20of%20IPs%3A%0AUse%20case%3A%0A) availability. ## Refunds Refunds follow the [refund policy](https://netdash.io/legal/refund). Read it before you order; this page doesn't restate it. Billing is also covered by the [Terms of Service](https://netdash.io/legal/terms). ## Larger orders and custom terms For more than 50,000 GB in one order, or for custom terms, contact sales. [Contact sales](mailto:sales@netdash.io?subject=Sales%20inquiry) ## Related - [Quickstart](https://netdash.io/docs/quickstart): Create an account, place a first order and send a request. - [Pricing](https://netdash.io/pricing): Every band rate for every plan, with a calculator. - [FAQ](https://netdash.io/faq): Short answers about plans, billing, sessions and setup. Questions about a payment or an order? [Email billing support](mailto:support@netdash.io?subject=Billing%20question). For setup problems, see [troubleshooting](https://netdash.io/docs/troubleshooting) or [email support](mailto:support@netdash.io?subject=Support%20request). --- # Use a proxy with Python requests, httpx and aiohttp > Pass the netdash proxy URL to both the `http` and `https` keys in requests, or to `proxy=` in httpx. The username carries the exit country and, for sticky sessions, a session ID and lifetime. Source: https://netdash.io/developers/python · Updated 2026-10-09 netdash works with any Python HTTP client that accepts an HTTP proxy URL, so there is no SDK to install. This page is the Python requests proxy reference, with the equivalent setup for httpx and aiohttp: authentication, sticky sessions, exit countries and the errors you are likely to meet. The examples run on Python 3.9 and later and use the Scale plan on the Americas gateway, `scale-us.netdash.io:2000`. Other plans use the same code with a different host and port, listed in the [proxy format reference](https://netdash.io/docs/proxy-format). ## Python requests proxy quick start Copy your username and password from the dashboard (Residential, then Generate), replace `USERNAME` and `PASSWORD`, and run: _Checked against a local test proxy._ Python: ```python import requests proxy = "http://USERNAME-cc-US:PASSWORD@scale-us.netdash.io:2000" proxies = {"http": proxy, "https": proxy} # set both, or HTTPS requests bypass the proxy response = requests.get("https://api.ipify.org?format=json", proxies=proxies, timeout=30) response.raise_for_status() print(response.text) ``` The script prints the exit IP the website saw, for example `{"ip":"203.0.113.10"}`. Set both keys. requests chooses a proxy by the scheme of the URL you request, so with only an `http` entry, requests to `https://` URLs bypass the proxy and leave from your own IP. The value of both keys starts with `http://`: netdash is an HTTP proxy, and HTTPS websites are reached through a CONNECT tunnel, so TLS still runs end to end between your client and the website. ## Proxy authentication in requests requests reads proxy credentials from the userinfo part of the proxy URL (`http://user:password@host:port`) and sends them to the gateway as Basic proxy authentication. Your netdash username does two jobs: it identifies your account and carries the connection options, `-cc-US` for the exit country and, for sticky sessions, `-session-{ID}-ttl-{MINUTES}`. One username and password work on every residential and mobile plan. In real code, read the credentials from the environment and build the URL in one place. Save this helper as `netdash_proxy.py`; the rest of this page imports it. _Checked against a local test proxy._ netdash_proxy.py: ```python import os import secrets from typing import Optional NETDASH_USERNAME = os.environ["NETDASH_USERNAME"] NETDASH_PASSWORD = os.environ["NETDASH_PASSWORD"] def proxy_url(country: str = "US", session_id: Optional[str] = None, ttl_minutes: int = 30, gateway: str = "scale-us.netdash.io:2000") -> str: """netdash proxy URL: rotating without session_id, sticky with one.""" cc = country.upper() cc = "GB" if cc == "UK" else cc username = f"{NETDASH_USERNAME}-cc-{cc}" if session_id is not None: username += f"-session-{session_id}-ttl-{ttl_minutes}" return f"http://{username}:{NETDASH_PASSWORD}@{gateway}" def new_session_id() -> str: return secrets.token_hex(4) # 8 characters, letters and digits ``` Export the two variables in the shell that runs your script, or load them from a `.env` file that stays out of version control. If a value ever contains characters such as `@`, `:` or `/`, percent-encode it with `urllib.parse.quote(value, safe="")` before it goes into the URL. > **Credentials** Keep `NETDASH_USERNAME` and `NETDASH_PASSWORD` in a `.env` file or your secret manager, never in source control or in a chat with an assistant. The helper never prints the URL, because the URL contains your password. ## Keep one exit IP for a task Some tasks need several requests from the same exit IP, for example a page followed by its pagination or a multi-step form you are permitted to use. Add a session ID and a lifetime to the username: reusing the same session ID keeps the same exit IP for the TTL while that IP stays available, and a new session ID starts a new session. The dashboard presets are 15 minutes, 30 minutes, 12 hours and 7 days; [rotating and sticky sessions](https://netdash.io/docs/sessions) explains how to choose. A `requests.Session` keeps cookies and reuses connections, which suits this kind of task. Pass `proxies=` on each request rather than setting `session.proxies`: the requests documentation warns that proxies from the environment overwrite `Session.proxies`. _Checked against a local test proxy._ sticky.py: ```python import requests from netdash_proxy import new_session_id, proxy_url sticky = proxy_url("US", session_id=new_session_id(), ttl_minutes=30) proxies = {"http": sticky, "https": sticky} with requests.Session() as session: for _ in range(2): response = session.get("https://api.ipify.org?format=json", proxies=proxies, timeout=30) response.raise_for_status() print(response.json()["ip"]) # the same exit IP both times ``` Session IDs use letters and digits only, without hyphens. `secrets.token_hex(4)` returns 8 characters from `0-9` and `a-f`, the same length the dashboard generates. ## Rotating proxies in Python A rotating session is the same username without the session part. You don't need a list of proxies or any rotation logic in your code: the gateway assigns exit IPs for you. Requests sent through one HTTPS tunnel share that tunnel's exit IP, so if consecutive requests must share an exit IP, use a sticky session instead of relying on connection reuse. This loop fetches a short list of pages at a steady rate. When a site answers `429 Too Many Requests`, it waits for the `Retry-After` delay and tries at most twice more; at `403` it stops, instead of retrying until something gets through. _Checked against a local test proxy._ rotating.py: ```python import time import requests from netdash_proxy import proxy_url URLS = [ "https://example.com/catalog?page=1", "https://example.com/catalog?page=2", "https://example.com/catalog?page=3", ] rotating = proxy_url("US") proxies = {"http": rotating, "https": rotating} def fetch(url: str) -> requests.Response: response = requests.get(url, proxies=proxies, timeout=30) for _ in range(2): # at most two more attempts, only after a 429 if response.status_code != 429: break retry_after = response.headers.get("Retry-After", "") time.sleep(int(retry_after) if retry_after.isdigit() else 60) response = requests.get(url, proxies=proxies, timeout=30) return response for url in URLS: response = fetch(url) if response.status_code == 403: print(f"403 from {url}: stopping. Check the site's terms or use its official API.") break response.raise_for_status() print(url, response.status_code, len(response.text)) time.sleep(1) # one request per second to this site ``` > Collect public data only, respect robots.txt and each site's terms, keep request rates reasonable and back off when a site slows you down. Don't collect personal data without a lawful basis, and don't access content behind logins or paywalls without authorization. netdash does not solve CAPTCHAs: if a site rate-limits or challenges your requests, slow down, check its terms, or use its official API. Read [how we expect netdash to be used](https://netdash.io/responsible-use). ## Choose the exit country The exit country is the `-cc-` part of the username: an ISO 3166-1 alpha-2 code in uppercase. Use `GB` for the United Kingdom, not `UK` (the helper maps it for you). Use the `-us` gateway for exits in the Americas and the `-eu` gateway for exits in Europe. Targeting is country-level; see the [country targeting syntax](https://netdash.io/docs/targeting) for details, and contact support to confirm availability for a specific country. _Checked against a local test proxy._ countries.py: ```python import requests from netdash_proxy import proxy_url for country in ("DE", "GB"): proxy = proxy_url(country, gateway="scale-eu.netdash.io:2000") response = requests.get("https://api.ipify.org?format=json", proxies={"http": proxy, "https": proxy}, timeout=30) response.raise_for_status() print(country, response.json()["ip"]) ``` ## httpx: sync and async clients httpx takes a single proxy URL with `proxy=` and uses it for both `http://` and `https://` requests. The credentials go in the URL, as with requests. The older `proxies=` argument was deprecated in httpx 0.26 and removed in 0.28, so code that still passes it fails with a `TypeError`; use `proxy=`, or `mounts=` if you need a different proxy per scheme. For concurrent requests, use `AsyncClient` and cap the number of requests in flight. netdash sets no per-account concurrency limit, so the limit you set per website is the one that counts. _Checked against a local test proxy._ Sync: ```python import httpx from netdash_proxy import proxy_url with httpx.Client(proxy=proxy_url("US"), timeout=30) as client: response = client.get("https://api.ipify.org?format=json") response.raise_for_status() print(response.json()) ``` Async: ```python import asyncio import httpx from netdash_proxy import proxy_url URLS = ["https://api.ipify.org?format=json"] * 10 async def fetch(client: httpx.AsyncClient, limit: asyncio.Semaphore, url: str) -> str: async with limit: response = await client.get(url) response.raise_for_status() return response.json()["ip"] async def main() -> None: limit = asyncio.Semaphore(5) # at most 5 requests in flight to this site async with httpx.AsyncClient(proxy=proxy_url("US"), timeout=30) as client: ips = await asyncio.gather(*(fetch(client, limit, url) for url in URLS)) print(ips) asyncio.run(main()) ``` ## aiohttp aiohttp takes the proxy per request with `proxy=`, and the credentials can sit in the proxy URL, so the same helper works. HTTPS websites go through a CONNECT tunnel. Unlike requests and httpx, aiohttp ignores proxy environment variables unless you create the session with `trust_env=True`. _Checked against a local test proxy._ aiohttp_example.py: ```python import asyncio import aiohttp from netdash_proxy import proxy_url async def main() -> None: timeout = aiohttp.ClientTimeout(total=30) async with aiohttp.ClientSession(timeout=timeout) as session: async with session.get("https://api.ipify.org?format=json", proxy=proxy_url("US")) as response: response.raise_for_status() print(await response.json()) asyncio.run(main()) ``` ## Environment variables, and why to avoid them requests and httpx also read `HTTP_PROXY`, `HTTPS_PROXY` and `NO_PROXY` from the environment, so this works without changing any code: _Checked against a local test proxy._ Shell: ```bash export HTTP_PROXY="http://USERNAME-cc-US:PASSWORD@scale-us.netdash.io:2000" export HTTPS_PROXY="http://USERNAME-cc-US:PASSWORD@scale-us.netdash.io:2000" export NO_PROXY="localhost,127.0.0.1" python3 app.py ``` app.py: ```python import requests print(requests.get("https://api.ipify.org?format=json", timeout=30).text) ``` A global `HTTPS_PROXY` applies to every request the process makes, including calls to model APIs, package registries and your own services. Those requests would go through netdash and count against your balance. Set the proxy on the client that fetches websites instead, and if a library picks up proxy variables you set for something else, turn that off with `session.trust_env = False` (requests) or `trust_env=False` (httpx). ## Errors and fixes | Symptom | Cause | Fix | |---|---|---| | Your own IP comes back for `https://` URLs | `proxies` has only the `http` key, so requests bypasses the proxy for HTTPS | Set both `http` and `https` | | `session.proxies` seems ignored | Proxies from the environment overwrite `Session.proxies` | Pass `proxies=` on each request, or set `session.trust_env = False` | | `ProxyError: ... Tunnel connection failed: 407 Proxy Authentication Required`, or `HTTPError: 407` for `http://` URLs | Wrong password, a mistyped base username or malformed parameters | Copy the credentials from the dashboard again; check `-cc-` and `-session-...-ttl-...` | | `ProxyError: ... Unable to connect to proxy` caused by `NewConnectionError` or `Connection refused`, or `ConnectTimeout` | Host and port don't match (for example `scale-us.netdash.io:1000`), or your network blocks the outbound port | Use the host and port from the endpoint table; allow outbound TCP to the gateway port | | `ProxyError` saying the proxy "appears to only use HTTP and not HTTPS" (`SSLError` on older urllib3) | Proxy URL written as `https://...` | Use `http://` for the proxy URL | | `httpx.ProxyError: 407 Proxy Authentication Required` or aiohttp `ClientHttpProxyError: 407` | Same as the requests 407 | Same fix | | `TypeError: ... unexpected keyword argument 'proxies'` (httpx) | httpx 0.28 removed `proxies=` | Use `proxy=` or `mounts=` | | `JSONDecodeError` from the test request | The test URL without `?format=json` returns plain text | Use `https://api.ipify.org?format=json` | | Wrong exit country | `UK` instead of `GB`, or a geolocation database that disagrees | Use `GB`; see the [country targeting syntax](https://netdash.io/docs/targeting) | | The script hangs | No timeout set | Always pass `timeout=` | > netdash forwards your traffic unchanged. It doesn't solve CAPTCHAs, render JavaScript or retry requests. Targeting is country-level. ## Next steps For a complete scraper that fetches, parses, stores and honors robots.txt, follow the [complete scraper tutorial](https://netdash.io/blog/web-scraping-python-proxies). Writing JavaScript as well? See [Node.js proxy setup](https://netdash.io/developers/nodejs), or browse all [proxy integration guides](https://netdash.io/developers). For first-time setup, start with the [quickstart](https://netdash.io/docs/quickstart). - [Scrapy proxy setup](https://netdash.io/developers/scrapy): Request meta or a downloader middleware, with the same proxy_url() helper and polite crawl settings. - [Rotating and sticky sessions](https://netdash.io/docs/sessions): Session IDs, TTL presets and when to rotate instead. - [Web data collection](https://netdash.io/solutions/web-scraping): Scheduled collection of public pages by country, at a rate each site can handle. The same client setup runs scheduled rank checks for [SEO monitoring](https://netdash.io/solutions/seo-monitoring). Stuck? See [troubleshooting](https://netdash.io/docs/troubleshooting) or email support with your username (without the password) and the error message. [Email support](mailto:support@netdash.io?subject=Proxy%20troubleshooting&body=Plan%3A%0AGateway%20host%3Aport%3A%0AUsername%20%28no%20password%29%3A%0ATime%20%28UTC%29%3A%0AWebsite%20domain%3A%0AError%20message%3A%0AClient%20and%20version%3A%0A) ## Sources - [Requests docs: Advanced usage, Proxies (both keys, Session.proxies warning, environment variables, Basic auth in the proxy URL)](https://requests.readthedocs.io/en/latest/user/advanced/#proxies) (accessed 2026-10-09) - [Requests API: Session.trust_env and Session.proxies](https://requests.readthedocs.io/en/latest/api/#requests.Session.trust_env) (accessed 2026-10-09) - [HTTPX docs: Proxies (proxy=, credentials in the URL, mounts)](https://www.python-httpx.org/advanced/proxies/) (accessed 2026-10-09) - [HTTPX docs: Environment variables (trust_env, HTTP_PROXY, HTTPS_PROXY, NO_PROXY)](https://www.python-httpx.org/environment_variables/) (accessed 2026-10-09) - [HTTPX changelog: proxies argument deprecated in 0.26.0, removed in 0.28.0](https://github.com/encode/httpx/blob/master/CHANGELOG.md) (accessed 2026-10-09) - [aiohttp docs: Advanced client usage, Proxy support](https://docs.aiohttp.org/en/stable/client_advanced.html#proxy-support) (accessed 2026-10-09) - [aiohttp docs: Client reference (proxy, trust_env, ClientTimeout)](https://docs.aiohttp.org/en/stable/client_reference.html) (accessed 2026-10-09) - [Python docs: secrets.token_hex](https://docs.python.org/3/library/secrets.html) (accessed 2026-10-09) --- # Use a proxy in Node.js with undici, fetch and axios > Create an undici `ProxyAgent` for the netdash gateway and pass it as the `dispatcher` to undici's `fetch`. Node's built-in `fetch` ignores `HTTPS_PROXY` unless you opt in. Source: https://netdash.io/developers/nodejs · Updated 2026-10-09 This Node.js proxy guide uses undici, the HTTP client behind Node's own `fetch`, installed as a package so you can give each request a proxy. netdash is a standard HTTP proxy, so there is no SDK: a `ProxyAgent` pointed at the gateway with your username and password is all it takes. The examples are ES modules with top-level `await`: save them as `.mjs` files, or set `"type": "module"` in `package.json`. They were checked with undici 7, which needs Node.js 20.18.1 or later. The current major version, undici 8, needs Node.js 22.19 or later, so on Node.js 20 install `undici@7`. ## Quick start: undici ProxyAgent with fetch Install undici with `npm install undici`, replace `USERNAME` and `PASSWORD` with the credentials from the dashboard (Residential, then Generate), and run the file: _Checked against a local test proxy._ Node.js: ```javascript import { ProxyAgent, fetch } from "undici"; const dispatcher = new ProxyAgent({ uri: "http://scale-us.netdash.io:2000", token: "Basic " + Buffer.from("USERNAME-cc-US:PASSWORD").toString("base64"), }); const res = await fetch("https://api.ipify.org?format=json", { dispatcher, signal: AbortSignal.timeout(30_000) }); console.log(await res.text()); ``` It prints the exit IP the website saw, for example `{"ip":"203.0.113.10"}`. The proxy URI starts with `http://` even though the website is HTTPS: netdash is an HTTP proxy, and HTTPS requests go through a CONNECT tunnel, so TLS still runs end to end between Node and the website. Import `fetch` from `undici` together with `ProxyAgent`, so the dispatcher and `fetch` come from the same package version. Node's built-in `fetch` is a separate copy of undici whose version depends on your Node.js release. ## Why built-in fetch ignores HTTPS_PROXY in a Node.js proxy setup Node's built-in `fetch` doesn't read `HTTP_PROXY` or `HTTPS_PROXY` by default. Setting the variables changes nothing until you opt in with `NODE_USE_ENV_PROXY=1` or the `--use-env-proxy` flag. `fetch` honors the opt-in from Node.js 22.21.0 or 24.0.0; the `http` and `https` modules, and the flag, need Node.js 22.21.0 or 24.5.0. Once enabled, requests use the proxy unless you pass your own agent or the host matches `NO_PROXY`: _Syntax-checked._ Shell: ```bash NODE_USE_ENV_PROXY=1 \ HTTPS_PROXY="http://USERNAME-cc-US:PASSWORD@scale-us.netdash.io:2000" \ NO_PROXY="localhost,127.0.0.1" \ node app.mjs ``` That switch applies to every request the process makes, including calls to model APIs, package registries and your own services. Those requests would go through netdash and count against your balance. An explicit dispatcher on the requests that fetch websites is easier to reason about, which is why the rest of this guide uses one. ## Authenticate the proxy undici offers two documented ways to send your credentials. The `token` option takes a complete `Proxy-Authorization` value, such as `Basic` plus the base64 of `username:password`, and takes precedence over anything else. Alternatively, put the credentials in the proxy URI: undici URL-decodes them and sends them as Basic authentication. The older `auth` option is deprecated. _Checked against a local test proxy._ uri-credentials.mjs: ```js import { ProxyAgent, fetch } from "undici"; const { NETDASH_USERNAME, NETDASH_PASSWORD } = process.env; const user = encodeURIComponent(`${NETDASH_USERNAME}-cc-US`); const pass = encodeURIComponent(NETDASH_PASSWORD); const dispatcher = new ProxyAgent(`http://${user}:${pass}@scale-us.netdash.io:2000`); const res = await fetch("https://api.ipify.org?format=json", { dispatcher, signal: AbortSignal.timeout(30_000), }); console.log(await res.json()); await dispatcher.close(); ``` > **Credentials** Keep `NETDASH_USERNAME` and `NETDASH_PASSWORD` in a `.env` file or your secret manager, never in source control or in a chat with an assistant. Don't log the proxy URI or the token: both contain your password. ## Sticky sessions and exit countries The username carries the options: `-cc-DE` sets the exit country, and `-session-{ID}-ttl-{MINUTES}` keeps one exit IP for the lifetime you set while that IP stays available. Reusing a session ID reuses the session; a new ID starts a new one. Session IDs use letters and digits only, and `randomBytes(4).toString("hex")` gives 8 of them, the same length the dashboard generates. Use `GB` for the United Kingdom, not `UK`, and the `-eu` gateway for exits in Europe. Put the helper in its own module: _Checked against a local test proxy._ netdash.mjs: ```js import { randomBytes } from "node:crypto"; import { ProxyAgent } from "undici"; const { NETDASH_USERNAME, NETDASH_PASSWORD } = process.env; export function netdashAgent({ country = "US", sessionId, ttl = 30, gateway = "http://scale-us.netdash.io:2000" } = {}) { const cc = country.toUpperCase() === "UK" ? "GB" : country.toUpperCase(); let username = `${NETDASH_USERNAME}-cc-${cc}`; if (sessionId) username += `-session-${sessionId}-ttl-${ttl}`; return new ProxyAgent({ uri: gateway, token: `Basic ${Buffer.from(`${username}:${NETDASH_PASSWORD}`).toString("base64")}`, }); } export const newSessionId = () => randomBytes(4).toString("hex"); ``` Then create one agent per sticky session, use it for every request of the task, and close it when the task ends: _Checked against a local test proxy._ sticky.mjs: ```js import { fetch } from "undici"; import { netdashAgent, newSessionId } from "./netdash.mjs"; // One agent per sticky session: German exit through the Europe gateway const agent = netdashAgent({ country: "DE", sessionId: newSessionId(), gateway: "http://scale-eu.netdash.io:2000" }); for (let i = 0; i < 2; i++) { const res = await fetch("https://api.ipify.org?format=json", { dispatcher: agent, signal: AbortSignal.timeout(30_000), }); console.log(await res.json()); // the same exit IP both times } await agent.close(); ``` The dashboard's sticky presets are 15 minutes, 30 minutes, 12 hours and 7 days; [rotating and sticky sessions](https://netdash.io/docs/sessions) explains how to choose. For rotating sessions, leave out `sessionId` and the gateway assigns exit IPs for you. ## axios with https-proxy-agent axios has its own `proxy` option and also reads `http_proxy` and `https_proxy` from the environment. For HTTPS websites, give axios an `httpsAgent` from `https-proxy-agent`, which opens a CONNECT tunnel and sends the credentials from the proxy URL as `Proxy-Authorization`, and set `proxy: false` so axios ignores its own proxy settings and the environment variables. Install both with `npm install axios https-proxy-agent`. _Checked against a local test proxy._ axios-example.mjs: ```js import axios from "axios"; import { HttpsProxyAgent } from "https-proxy-agent"; const { NETDASH_USERNAME, NETDASH_PASSWORD } = process.env; const proxy = `http://${NETDASH_USERNAME}-cc-US:${NETDASH_PASSWORD}@scale-us.netdash.io:2000`; const res = await axios.get("https://api.ipify.org?format=json", { httpsAgent: new HttpsProxyAgent(proxy), proxy: false, // no built-in proxy handling, no proxy environment variables timeout: 30_000, }); console.log(res.data); ``` For plain `http://` websites, pass `httpAgent: new HttpProxyAgent(proxy)` from the `http-proxy-agent` package as well. Recent `https-proxy-agent` releases are ES modules only, which suits the `.mjs` files used here. ## Concurrency without overloading a site netdash sets no per-account concurrency limit, so the limit that matters is the one each website can handle. A small worker pool over one agent keeps the number of requests in flight fixed, and it stops as soon as the site pushes back with `403` or `429`: _Checked against a local test proxy._ pool.mjs: ```js import { fetch } from "undici"; import { netdashAgent } from "./netdash.mjs"; const urls = Array.from({ length: 10 }, (_, i) => `https://example.com/catalog?page=${i + 1}`); const queue = [...urls]; const results = []; const agent = netdashAgent({ country: "US" }); let stopped = false; async function worker() { while (queue.length > 0 && !stopped) { const url = queue.shift(); const res = await fetch(url, { dispatcher: agent, signal: AbortSignal.timeout(30_000) }); const body = await res.text(); if (res.status === 403 || res.status === 429) { stopped = true; // the site is pushing back: stop, then check its terms and limits console.log(`${res.status} from ${url}, stopping`); return; } results.push({ url, status: res.status, bytes: body.length }); } } await Promise.all(Array.from({ length: 5 }, worker)); // 5 requests in flight at most console.table(results); await agent.close(); ``` > Collect public data only, respect robots.txt and each site's terms, keep request rates reasonable and back off when a site slows you down. Don't collect personal data without a lawful basis, and don't access content behind logins or paywalls without authorization. netdash does not solve CAPTCHAs: if a site rate-limits or challenges your requests, slow down, check its terms, or use its official API. Read [how we expect netdash to be used](https://netdash.io/responsible-use). ## Errors and fixes When undici's `fetch` fails, the message is usually just `TypeError: fetch failed`. Log `err.cause`, and the `cause` of that error in turn, to see what went wrong. | Symptom | Cause | Fix | |---|---|---| | Requests leave from your own IP | Built-in `fetch` ignores `HTTPS_PROXY` by default | Pass a `dispatcher`, or opt in with `NODE_USE_ENV_PROXY=1` knowing it affects the whole process | | `TypeError: fetch failed` with the cause `Request was cancelled.`, whose own cause is `Proxy response (407) !== 200 when HTTP Tunneling` (undici 7) | The gateway rejected the credentials with `407 Proxy Authentication Required` | Check the password and the username parameters (`-cc-`, `-session-...-ttl-...`) | | `TypeError: fetch failed` with the cause `ECONNREFUSED` or `UND_ERR_CONNECT_TIMEOUT` | Wrong host and port pair, or your network blocks the outbound port | Use the host and port from the endpoint table; allow outbound TCP to the gateway port | | Errors after passing a dispatcher to the built-in `fetch` | Node's bundled `fetch` and the `undici` package can be different versions with different dispatcher interfaces | Import `fetch` and `ProxyAgent` from the same `undici` package | | HTTPS through axios fails or goes direct | axios' built-in `proxy` option or the environment variables are in use | Use `httpsAgent` with `proxy: false` | | `SyntaxError: Cannot use import statement outside a module` | The file is loaded as CommonJS (an older Node.js release, or `"type": "commonjs"` in `package.json`) | Use `.mjs` or set `"type": "module"` | | The request never finishes | No timeout set | Pass `signal: AbortSignal.timeout(30_000)` (axios: `timeout`) | > netdash forwards your traffic unchanged. It doesn't solve CAPTCHAs, render JavaScript or retry requests. Targeting is country-level. ## Next steps Driving a browser instead of an HTTP client? The [Playwright proxy setup](https://netdash.io/developers/playwright) covers per-browser and per-context proxies. For Python, see the [Python requests proxy guide](https://netdash.io/developers/python), and for data collection at scale, [web data collection](https://netdash.io/solutions/web-scraping). All other tools are in the [proxy integration guides](https://netdash.io/developers). New to netdash? Start with the [quickstart](https://netdash.io/docs/quickstart) and keep the [proxy format reference](https://netdash.io/docs/proxy-format) at hand. - [Puppeteer proxy setup](https://netdash.io/developers/puppeteer): An authenticated proxy per browser or per context, with the exit country in the username. - [Troubleshooting](https://netdash.io/docs/troubleshooting): 407 errors, timeouts, requests that skip the proxy and the wrong exit country. - [Browser automation](https://netdash.io/solutions/browser-automation): End-to-end tests and permitted browser workflows from the countries your users are in. Stuck? See [troubleshooting](https://netdash.io/docs/troubleshooting) or email support with your username (without the password) and the error message. [Email support](mailto:support@netdash.io?subject=Proxy%20troubleshooting&body=Plan%3A%0AGateway%20host%3Aport%3A%0AUsername%20%28no%20password%29%3A%0ATime%20%28UTC%29%3A%0AWebsite%20domain%3A%0AError%20message%3A%0AClient%20and%20version%3A%0A) ## Sources - [undici docs: ProxyAgent (uri, token, deprecated auth, credentials in the URI, close)](https://github.com/nodejs/undici/blob/main/docs/docs/api/ProxyAgent.md) (accessed 2026-10-09) - [undici docs: Migrating from v7 to v8 (Node.js requirement, built-in fetch compatibility)](https://undici.nodejs.org/best-practices/migrating-from-v7-to-v8) (accessed 2026-10-09) - [Node.js Learn: Enterprise network configuration (NODE_USE_ENV_PROXY, --use-env-proxy, versions)](https://nodejs.org/en/learn/http/enterprise-network-configuration) (accessed 2026-10-09) - [axios docs: Request config (proxy, proxy: false, httpAgent, httpsAgent, timeout)](https://axios.rest/pages/advanced/request-config) (accessed 2026-10-09) - [proxy-agents: https-proxy-agent README and source (CONNECT, Proxy-Authorization from the URL)](https://github.com/TooTallNate/proxy-agents/tree/main/packages/https-proxy-agent) (accessed 2026-10-09) - [npm registry: undici package metadata (engines)](https://registry.npmjs.org/undici/latest) (accessed 2026-10-09) - [npm registry: https-proxy-agent package metadata (ES module only)](https://registry.npmjs.org/https-proxy-agent/latest) (accessed 2026-10-09) --- # Use an HTTP proxy in Go with net/http > Set `Transport.Proxy` to `http.ProxyURL` with a URL that carries your netdash username and password. Go sends them to the gateway in a `Proxy-Authorization` header, for plain HTTP requests and for the CONNECT tunnel that HTTPS uses. Source: https://netdash.io/developers/go · Updated 2026-10-09 `net/http` handles a Go HTTP proxy connection on its own: give an `http.Transport` a proxy URL with your credentials in it, and every request made through that client goes through the netdash gateway. There is no SDK. The examples are complete `package main` programs that use only the standard library and were checked with Go 1.24. They use the Scale plan on the Americas gateway, `scale-us.netdash.io:2000`; other plans differ only in host and port, listed in the [proxy format reference](https://netdash.io/docs/proxy-format). ## Quick start Replace `USERNAME` and `PASSWORD` with the credentials from the dashboard (Residential, then Generate), save the file as `main.go` and run `go run main.go`: _Checked against a local test proxy._ Go: ```go package main import ( "fmt" "io" "net/http" "net/url" "time" ) func main() { proxyURL, err := url.Parse("http://USERNAME-cc-US:PASSWORD@scale-us.netdash.io:2000") if err != nil { panic(err) } client := &http.Client{ Transport: &http.Transport{Proxy: http.ProxyURL(proxyURL)}, Timeout: 30 * time.Second, } resp, err := client.Get("https://api.ipify.org?format=json") if err != nil { panic(err) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { panic(err) } fmt.Println(string(body)) } ``` It prints the exit IP the website saw, for example `{"ip":"203.0.113.10"}`. The proxy URL starts with `http://` even for HTTPS websites: netdash is an HTTP proxy, and Go opens a CONNECT tunnel through it, so TLS still runs end to end between your program and the website. ## Go HTTP proxy authentication `net/http` documents that when the proxy URL contains a userinfo part, the request to the proxy carries the username and password in a `Proxy-Authorization` header. In our checks against a local test gateway that requires authentication, Go sent the header both on plain HTTP requests and on the CONNECT request for HTTPS websites. Build the URL with `url.UserPassword` rather than string formatting, so the userinfo is escaped correctly whatever your password contains. Your netdash username carries the options: `-cc-US` sets the exit country, and `-session-{ID}-ttl-{MINUTES}` makes the session sticky. Save this helper as `netdash.go`: _Checked against a local test proxy._ netdash.go: ```go package main import ( "crypto/rand" "encoding/hex" "fmt" "net/http" "net/url" "os" "strings" "time" ) // netdashClient returns a client that sends every request through a netdash gateway. // Leave sessionID empty for a rotating session; set it for a sticky one. func netdashClient(country, sessionID string, ttl int, gateway string) *http.Client { cc := strings.ToUpper(country) if cc == "UK" { cc = "GB" } username := os.Getenv("NETDASH_USERNAME") + "-cc-" + cc if sessionID != "" { username += fmt.Sprintf("-session-%s-ttl-%d", sessionID, ttl) } proxyURL := &url.URL{ Scheme: "http", User: url.UserPassword(username, os.Getenv("NETDASH_PASSWORD")), Host: gateway, // e.g. "scale-us.netdash.io:2000" } return &http.Client{ Transport: &http.Transport{Proxy: http.ProxyURL(proxyURL)}, Timeout: 30 * time.Second, } } // newSessionID returns 8 random characters from 0-9 and a-f for a sticky session. func newSessionID() (string, error) { b := make([]byte, 4) if _, err := rand.Read(b); err != nil { return "", err } return hex.EncodeToString(b), nil } ``` Then use it from a second file in the same directory and run both with `go run netdash.go main.go`: _Checked against a local test proxy._ main.go: ```go package main import ( "fmt" "io" "log" ) func main() { client := netdashClient("US", "", 0, "scale-us.netdash.io:2000") resp, err := client.Get("https://api.ipify.org?format=json") if err != nil { log.Fatal(err) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(body)) } ``` > **Credentials** Keep `NETDASH_USERNAME` and `NETDASH_PASSWORD` in a `.env` file or your secret manager, never in source control or in a chat with an assistant. If you log a proxy URL, log `proxyURL.Redacted()`, which replaces the password with `xxxxx`. ## Sticky sessions: one client per session A sticky session keeps the same exit IP for the TTL you set while that IP stays available. Because the session lives in the username, and the username lives in the client's transport, use one `*http.Client` per session ID: reuse it for every request of the task, and call `CloseIdleConnections()` when the task is done. A new session ID starts a new session. The dashboard's sticky presets are 15 minutes, 30 minutes, 12 hours and 7 days; [rotating and sticky sessions](https://netdash.io/docs/sessions) explains how to choose. _Checked against a local test proxy._ sticky.go: ```go package main import ( "fmt" "io" "log" ) func main() { sessionID, err := newSessionID() if err != nil { log.Fatal(err) } // One client per sticky session: German exit through the Europe gateway, 30-minute TTL client := netdashClient("DE", sessionID, 30, "scale-eu.netdash.io:2000") defer client.CloseIdleConnections() for i := 0; i < 2; i++ { resp, err := client.Get("https://api.ipify.org?format=json") if err != nil { log.Fatal(err) } body, err := io.ReadAll(resp.Body) resp.Body.Close() if err != nil { log.Fatal(err) } fmt.Println(string(body)) // the same exit IP both times } } ``` Run it with `go run netdash.go sticky.go`. For a rotating session, pass an empty session ID and the gateway assigns exit IPs for you. Requests sent through one HTTPS tunnel share that tunnel's exit IP, so when consecutive requests must share an exit IP, use a sticky session rather than relying on connection reuse. ## Exit countries The first argument of `netdashClient` is the exit country, an ISO 3166-1 alpha-2 code: `netdashClient("DE", "", 0, "scale-eu.netdash.io:2000")` exits in Germany. Use `GB` for the United Kingdom, not `UK` (the helper maps it). Use the `-us` gateway for exits in the Americas and the `-eu` gateway for exits in Europe. Targeting is country-level; the [country targeting syntax](https://netdash.io/docs/targeting) has the details. ## Timeouts, contexts and retries `Client.Timeout` caps each request, including reading the body; a zero value means no timeout, so always set one. For a deadline across several attempts, pass a context with `http.NewRequestWithContext`. Retry only what is worth retrying: network errors, 5xx responses and `429 Too Many Requests` (after the `Retry-After` delay), with a cap on attempts. A `403` means stop. _Checked against a local test proxy._ retry.go: ```go package main import ( "context" "fmt" "io" "log" "net/http" "strconv" "time" ) // getWithRetry retries network errors, 5xx and 429, three attempts at most, and honors // Retry-After on 429. Every other status, including 403, goes back to the caller. func getWithRetry(ctx context.Context, client *http.Client, target string) (*http.Response, error) { wait := time.Second for attempt := 1; ; attempt++ { req, err := http.NewRequestWithContext(ctx, http.MethodGet, target, nil) if err != nil { return nil, err } resp, err := client.Do(req) retryable := err != nil || resp.StatusCode >= 500 || resp.StatusCode == http.StatusTooManyRequests if !retryable || attempt == 3 { return resp, err } delay := wait if resp != nil { if s, convErr := strconv.Atoi(resp.Header.Get("Retry-After")); convErr == nil && resp.StatusCode == http.StatusTooManyRequests { delay = time.Duration(s) * time.Second } resp.Body.Close() } select { case <-ctx.Done(): return nil, ctx.Err() case <-time.After(delay): } wait *= 2 } } func main() { client := netdashClient("US", "", 0, "scale-us.netdash.io:2000") ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) defer cancel() resp, err := getWithRetry(ctx, client, "https://api.ipify.org?format=json") if err != nil { log.Fatal(err) } defer resp.Body.Close() if resp.StatusCode == http.StatusForbidden { log.Fatal("403: stop here, then check the site's terms or use its official API") } body, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(resp.Status, string(body)) } ``` ## Environment variables and DefaultTransport `http.Get`, `http.DefaultClient` and every client without its own transport use `http.DefaultTransport`, which picks a proxy with `ProxyFromEnvironment`: it reads `HTTP_PROXY`, `HTTPS_PROXY` and `NO_PROXY`, and never proxies requests to `localhost`. Exporting `HTTPS_PROXY` therefore routes every such client in the process through netdash, including SDKs that call model APIs or your own services, and those requests count against your balance. Give the clients that fetch websites an explicit `Transport`, as `netdashClient` does, and leave the environment alone. ## Concurrency with a worker pool netdash sets no per-account concurrency limit; the website sets the pace. This pool runs four workers that share one ticker, so the site sees at most two requests per second however many workers you add, and the pool stops as soon as the site answers `403` or `429`: _Checked against a local test proxy._ pool.go: ```go package main import ( "context" "fmt" "io" "net/http" "sync" "time" ) func main() { client := netdashClient("US", "", 0, "scale-us.netdash.io:2000") urls := make([]string, 0, 10) for i := 1; i <= 10; i++ { urls = append(urls, fmt.Sprintf("https://example.com/catalog?page=%d", i)) } ctx, stop := context.WithCancel(context.Background()) defer stop() pace := time.NewTicker(500 * time.Millisecond) // at most two requests per second to this site defer pace.Stop() jobs := make(chan string, len(urls)) for _, u := range urls { jobs <- u } close(jobs) results := make(chan string) var wg sync.WaitGroup for w := 0; w < 4; w++ { wg.Add(1) go func() { defer wg.Done() for u := range jobs { select { case <-ctx.Done(): return case <-pace.C: } req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil) if err != nil { results <- fmt.Sprintf("%s: %v", u, err) continue } resp, err := client.Do(req) if err != nil { results <- fmt.Sprintf("%s: %v", u, err) continue } n, _ := io.Copy(io.Discard, resp.Body) resp.Body.Close() if resp.StatusCode == http.StatusForbidden || resp.StatusCode == http.StatusTooManyRequests { stop() // the site is pushing back: stop, then check its terms and limits } results <- fmt.Sprintf("%s: %s, %d bytes", u, resp.Status, n) } }() } go func() { wg.Wait() close(results) }() for r := range results { fmt.Println(r) } } ``` Run it with `go run netdash.go pool.go`. > Collect public data only, respect robots.txt and each site's terms, keep request rates reasonable and back off when a site slows you down. Don't collect personal data without a lawful basis, and don't access content behind logins or paywalls without authorization. Test only systems you own or are authorized to test. netdash does not solve CAPTCHAs: if a site rate-limits or challenges your requests, slow down, check its terms, or use its official API. Read [how we expect netdash to be used](https://netdash.io/responsible-use). ## Errors and fixes | Symptom | Cause | Fix | |---|---|---| | `panic: runtime error: invalid memory address or nil pointer dereference` at `resp.Body.Close()` | The error from `Get` or `Do` was ignored, so `resp` is nil | Check `err` before using `resp` | | `Get "https://...": Proxy Authentication Required` | The gateway rejected the credentials during CONNECT | Check the password and the username parameters (`-cc-`, `-session-...-ttl-...`) | | `407 Proxy Authentication Required` as the response status for an `http://` URL | Same cause; for plain HTTP, Go returns the gateway's 407 as a response, not an error | Same fix | | `proxyconnect tcp: dial tcp ...: connect: connection refused` | Wrong host and port pair | Use the host and port from the endpoint table | | `context deadline exceeded (Client.Timeout exceeded while awaiting headers)` or `proxyconnect tcp: ... i/o timeout` | Your network blocks the outbound port, or the host is wrong | Allow outbound TCP to the gateway port; check the endpoint table | | Connections pile up and memory grows | Response bodies are not closed | `defer resp.Body.Close()` right after the error check | | Requests to a local service go through the proxy, or skip it, unexpectedly | `ProxyFromEnvironment` rules: `NO_PROXY`, and `localhost` is never proxied | Use an explicit `Transport` for each purpose | | `io/ioutil` deprecation warnings | Code from an older example | Use `io.ReadAll` and `io.Discard` | > netdash forwards your traffic unchanged. It doesn't solve CAPTCHAs, render JavaScript or retry requests. Targeting is country-level. ## Next steps The same clients run scheduled rank checks for [SEO monitoring](https://netdash.io/solutions/seo-monitoring). If you also write Python, the [Python requests proxy guide](https://netdash.io/developers/python) uses the same username conventions, and the other [proxy integration guides](https://netdash.io/developers) cover Node.js, Scrapy and browser tools. If you don't have credentials yet, the [quickstart](https://netdash.io/docs/quickstart) takes you from a new account to the first request. - [Python proxy setup](https://netdash.io/developers/python): requests, httpx and aiohttp with the same username syntax and helper names. - [Rotating and sticky sessions](https://netdash.io/docs/sessions): Session IDs, TTL presets and when to rotate instead. - [Security testing](https://netdash.io/solutions/security-testing): Authorized tests of your own geo rules, rate limits and WAF policies from other countries. Stuck? See [troubleshooting](https://netdash.io/docs/troubleshooting) or email support with your username (without the password) and the error message. [Email support](mailto:support@netdash.io?subject=Proxy%20troubleshooting&body=Plan%3A%0AGateway%20host%3Aport%3A%0AUsername%20%28no%20password%29%3A%0ATime%20%28UTC%29%3A%0AWebsite%20domain%3A%0AError%20message%3A%0AClient%20and%20version%3A%0A) ## Sources - [Go net/http: Transport.Proxy (userinfo sent as Proxy-Authorization), ProxyURL, ProxyFromEnvironment, DefaultTransport, Client.Timeout](https://pkg.go.dev/net/http) (accessed 2026-10-09) - [Go net/http: ProxyFromEnvironment (HTTP_PROXY, HTTPS_PROXY, NO_PROXY, localhost)](https://pkg.go.dev/net/http#ProxyFromEnvironment) (accessed 2026-10-09) - [Go net/url: UserPassword and URL.Redacted](https://pkg.go.dev/net/url#UserPassword) (accessed 2026-10-09) --- # Configure a proxy in Scrapy > Set `request.meta['proxy']` to the netdash proxy URL, or let a small downloader middleware set it for every request. Scrapy's `HttpProxyMiddleware` turns the URL's credentials into the proxy authentication header. Source: https://netdash.io/developers/scrapy · Updated 2026-10-09 Scrapy has proxy support built in, so a Scrapy proxy setup needs no extra package: put the netdash proxy URL, credentials included, in a request's `proxy` meta key, and Scrapy's own `HttpProxyMiddleware` handles the authentication. Setting the meta key by hand suits a single spider or a quick test. For a project, a short downloader middleware sets it on every request, including the ones Scrapy creates itself. The examples need Scrapy 2.13 or later (Scrapy 2.14 and later need Python 3.10) and use the Scale plan on the Americas gateway, `scale-us.netdash.io:2000`. Other plans differ only in host and port, listed in the [proxy format reference](https://netdash.io/docs/proxy-format). ## Scrapy proxy quick start: the proxy request meta key This spider requests one page through netdash and prints the exit IP. It carries the same `proxy_url()` helper as the [Python guide](https://netdash.io/developers/python), which reads your credentials from `NETDASH_USERNAME` and `NETDASH_PASSWORD`. Run it with `scrapy runspider ip_spider.py`: _Syntax-checked._ ip_spider.py: ```python import os import secrets from typing import Optional import scrapy NETDASH_USERNAME = os.environ["NETDASH_USERNAME"] NETDASH_PASSWORD = os.environ["NETDASH_PASSWORD"] def proxy_url(country: str = "US", session_id: Optional[str] = None, ttl_minutes: int = 30, gateway: str = "scale-us.netdash.io:2000") -> str: """netdash proxy URL: rotating without session_id, sticky with one.""" cc = country.upper() cc = "GB" if cc == "UK" else cc username = f"{NETDASH_USERNAME}-cc-{cc}" if session_id is not None: username += f"-session-{session_id}-ttl-{ttl_minutes}" return f"http://{username}:{NETDASH_PASSWORD}@{gateway}" def new_session_id() -> str: return secrets.token_hex(4) # 8 characters, letters and digits class IpSpider(scrapy.Spider): name = "ip" custom_settings = {"ROBOTSTXT_OBEY": True, "DOWNLOAD_TIMEOUT": 30} async def start(self): yield scrapy.Request("https://api.ipify.org?format=json", meta={"proxy": proxy_url("US")}) def parse(self, response): yield response.json() # {"ip": "203.0.113.10"} ``` How it works, according to the Scrapy documentation: - The proxy URL may contain credentials (`http://username:password@host:port`); `HttpProxyMiddleware` sends them to the gateway as proxy authentication. - A `proxy` meta value takes precedence over the `http_proxy` and `https_proxy` environment variables, and ignores `no_proxy`. The proxy URL starts with `http://`, also for HTTPS websites: netdash is an HTTP proxy, and HTTPS requests go through a CONNECT tunnel. Meta applies only to the requests you set it on. Requests that Scrapy creates itself, such as the `robots.txt` check, don't carry your meta, so they go out without the proxy. The middleware below closes that gap. ## A downloader middleware for every request The middleware reads your credentials once and builds the proxy URL for each request from three optional meta keys, `netdash_country`, `netdash_session` and `netdash_ttl`, falling back to spider attributes of the same name without the prefix. It leaves alone any request that already has a `proxy` key, so `meta={"proxy": None}` still sends a request directly. Enable it at order 350, so it runs before the built-in `HttpProxyMiddleware` at 750. _Syntax-checked._ middlewares.py: ```python import os class NetdashProxyMiddleware: """Sets a netdash proxy URL on every request that doesn't already have one.""" def __init__(self, crawler, username, password, gateway): self.crawler = crawler self.username = username self.password = password self.gateway = gateway @classmethod def from_crawler(cls, crawler): return cls( crawler, os.environ["NETDASH_USERNAME"], os.environ["NETDASH_PASSWORD"], crawler.settings.get("NETDASH_GATEWAY", "scale-us.netdash.io:2000"), ) def process_request(self, request, spider=None): # spider=None: Scrapy 2.13 passes the spider, 2.14 and later don't. if "proxy" in request.meta: return None spider = self.crawler.spider country = str(request.meta.get("netdash_country", getattr(spider, "country", "US"))).upper() country = "GB" if country == "UK" else country user = f"{self.username}-cc-{country}" session = request.meta.get("netdash_session", getattr(spider, "session", None)) if session: ttl = request.meta.get("netdash_ttl", getattr(spider, "ttl", 30)) user += f"-session-{session}-ttl-{ttl}" request.meta["proxy"] = f"http://{user}:{self.password}@{self.gateway}" return None ``` settings.py: ```python DOWNLOADER_MIDDLEWARES = { "myproject.middlewares.NetdashProxyMiddleware": 350, } NETDASH_GATEWAY = "scale-us.netdash.io:2000" ``` Replace `myproject` with your project's module name. Scrapy 2.14 deprecated requiring a `spider` argument in custom downloader middleware methods in favor of the crawler's `spider` attribute; the `spider=None` default keeps the middleware working on Scrapy 2.13 and on newer releases without a deprecation warning. > **Credentials** Keep `NETDASH_USERNAME` and `NETDASH_PASSWORD` in a `.env` file or your secret manager, never in source control, in `settings.py` or in a chat with an assistant. The middleware reads them from the environment when the crawler starts. ## Sticky sessions per crawl or per item Without a session ID, every request uses a rotating session and the gateway assigns exit IPs for you. A sticky session keeps the same exit IP for the TTL you set while that IP stays available, and reusing the session ID reuses the session. Use letters and digits only; the dashboard generates 8 lowercase characters. - **One session for the whole crawl**: pass it as a spider argument, `scrapy crawl prices -a session=a1b2c3d4 -a ttl=30`. The `-a` arguments become spider attributes, which the middleware reads. - **One session per item**: set `netdash_session` in the meta of the requests that belong together. Meta isn't inherited by follow-up requests, so pass it on explicitly: _Syntax-checked._ prices.py: ```python import secrets import scrapy class PricesSpider(scrapy.Spider): name = "prices" start_urls = ["https://example.com/catalog"] def parse(self, response): for href in response.css("a.product::attr(href)").getall(): # one sticky session per product: its pages share an exit IP yield response.follow(href, self.parse_product, meta={"netdash_session": secrets.token_hex(4)}) def parse_product(self, response): specs = response.css("a.specs::attr(href)").get() if specs: yield response.follow(specs, self.parse_specs, meta={"netdash_session": response.meta["netdash_session"]}) def parse_specs(self, response): yield {"url": response.url, "title": response.css("h1::text").get()} ``` TTL values match the dashboard presets: 15, 30, 720 (12 hours) or 10080 (7 days) minutes. [Rotating and sticky sessions](https://netdash.io/docs/sessions) explains how to choose. ## Country per spider run The exit country is a spider argument too, so the same spider can run once per market. European exits use the Europe gateway, which `-s` sets for that run only: _Syntax-checked._ Shell: ```bash scrapy crawl prices -a country=DE -s NETDASH_GATEWAY=scale-eu.netdash.io:2000 ``` `-a country=DE` becomes the spider attribute `country` that the middleware reads, and `-s` overrides the one custom setting. Use ISO 3166-1 alpha-2 codes, and `GB` for the United Kingdom, not `UK`. Targeting is country-level; see the [country targeting syntax](https://netdash.io/docs/targeting). ## Crawl politely netdash sets no per-account concurrency limit, so the limits in `settings.py` are the ones that protect the websites you crawl. Start conservative: _Syntax-checked._ settings.py: ```python ROBOTSTXT_OBEY = True AUTOTHROTTLE_ENABLED = True AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0 DOWNLOAD_DELAY = 1.0 CONCURRENT_REQUESTS_PER_DOMAIN = 4 DOWNLOAD_TIMEOUT = 30 RETRY_TIMES = 2 USER_AGENT = "examplebot/1.0 (+https://example.com/bot)" ``` AutoThrottle raises the delay when responses slow down and never goes below `DOWNLOAD_DELAY`; `CONCURRENT_REQUESTS_PER_DOMAIN` stays a hard cap on top of it. A low `RETRY_TIMES` keeps failures from multiplying traffic. Name your crawler in `USER_AGENT` and link to a page that explains it, so site owners can reach you. > Collect public data only, respect robots.txt and each site's terms, keep request rates reasonable and back off when a site slows you down. Don't collect personal data without a lawful basis, and don't access content behind logins or paywalls without authorization. netdash does not solve CAPTCHAs: if a site rate-limits or challenges your requests, slow down, check its terms, or use its official API. Read [how we expect netdash to be used](https://netdash.io/responsible-use). ## Proxy credentials since Scrapy 2.6.2 Scrapy 2.6.2 changed how `HttpProxyMiddleware` treats the `Proxy-Authorization` header when the `proxy` meta key changes between requests. It matters if you switch countries or sessions within one crawl. > If the `proxy` meta value contains credentials, the header is updated to those credentials. If it has no credentials, the header is removed, unless it was set for the same proxy URL. If there is no `proxy` meta value, the header is removed. In practice: put the credentials in every `proxy` URL (the middleware above does), never set `Proxy-Authorization` by hand, and don't combine this setup with third-party proxy rotation middlewares. ## Errors and fixes | Symptom | Cause | Fix | |---|---|---| | `407` after switching proxies within a crawl | Since 2.6.2 the header is removed when new `proxy` meta has no credentials | Put the credentials in every `proxy` URL | | Proxy settings have no effect | Settings such as `PROXY` or `ROTATING_PROXY_LIST` are not Scrapy settings; they only work with the third-party middleware that defines them | Use `meta['proxy']` or the middleware above | | `robots.txt` requests leave from your own IP | Requests Scrapy creates itself don't carry your meta | Use the middleware, which sets the proxy on every request | | HTTPS requests fail with an `https://` proxy URL | The default HTTP/1.1 download handler supports HTTPS proxies only for HTTP destinations | Use `http://` for the proxy URL | | Proxy ignored | The HTTP/2 download handler (`H2DownloadHandler`) doesn't support the `proxy` meta key, and third-party handlers may not either | Keep the default HTTP/1.1 handler for proxied requests | | `ScrapyDeprecationWarning` about a spider argument | A middleware method requires `spider` on Scrapy 2.14 or later | Give `spider` a default of `None` and use `self.crawler.spider` | | The middleware never runs | Wrong module path or order in `DOWNLOADER_MIDDLEWARES` | Check the path, and keep the order below 750 | | The crawl puts load on a site | No throttling | Enable AutoThrottle, set a delay and a per-domain cap | > netdash forwards your traffic unchanged. It doesn't solve CAPTCHAs, render JavaScript or retry requests. Targeting is country-level. ## Next steps For a complete Python pipeline without Scrapy, see the [complete scraper tutorial](https://netdash.io/blog/web-scraping-python-proxies). Collecting training data? Read [responsible AI training data collection](https://netdash.io/blog/ai-training-data-scraping) first. Price checks by market are covered in [e-commerce price monitoring](https://netdash.io/solutions/ecommerce), and every other tool is in the [proxy integration guides](https://netdash.io/developers). New to netdash? Start with the [quickstart](https://netdash.io/docs/quickstart). To size a pipeline, see [pricing](https://netdash.io/pricing): residential GB are sold in one-time orders with graduated per-GB pricing, and purchased GB never expire. - [Python proxy setup](https://netdash.io/developers/python): requests, httpx and aiohttp with the same proxy_url() helper. - [Country targeting](https://netdash.io/docs/targeting): ISO country codes in the username, GB for the United Kingdom, and the matching gateway. - [Web data collection](https://netdash.io/solutions/web-scraping): Scheduled collection of public pages by country, at a rate each site can handle. Stuck? See [troubleshooting](https://netdash.io/docs/troubleshooting) or email support with your username (without the password) and the error message. [Email support](mailto:support@netdash.io?subject=Proxy%20troubleshooting&body=Plan%3A%0AGateway%20host%3Aport%3A%0AUsername%20%28no%20password%29%3A%0ATime%20%28UTC%29%3A%0AWebsite%20domain%3A%0AError%20message%3A%0AClient%20and%20version%3A%0A) ## Sources - [Scrapy docs: HttpProxyMiddleware (proxy meta key, credentials in the URL, environment variables, precedence, download handler support)](https://docs.scrapy.org/en/latest/topics/downloader-middleware.html#module-scrapy.downloadermiddlewares.httpproxy) (accessed 2026-10-09) - [Scrapy docs: Writing your own downloader middleware (process_request signature)](https://docs.scrapy.org/en/latest/topics/downloader-middleware.html#writing-your-own-downloader-middleware) (accessed 2026-10-09) - [Scrapy release notes: 2.6.2 (Proxy-Authorization handling), 2.13.0 (start()), 2.14.0 (spider argument of downloader middleware methods deprecated)](https://docs.scrapy.org/en/latest/news.html) (accessed 2026-10-09) - [Scrapy docs: Settings (DOWNLOADER_MIDDLEWARES_BASE order, ROBOTSTXT_OBEY, DOWNLOAD_DELAY, CONCURRENT_REQUESTS_PER_DOMAIN, DOWNLOAD_TIMEOUT)](https://docs.scrapy.org/en/latest/topics/settings.html) (accessed 2026-10-09) - [Scrapy docs: AutoThrottle extension](https://docs.scrapy.org/en/latest/topics/autothrottle.html) (accessed 2026-10-09) - [Scrapy docs: Spiders (start(), spider arguments with -a)](https://docs.scrapy.org/en/latest/topics/spiders.html) (accessed 2026-10-09) - [Scrapy source: robots.txt middleware request and middleware spider-argument check](https://github.com/scrapy/scrapy/blob/master/scrapy/downloadermiddlewares/robotstxt.py) (accessed 2026-10-09) --- # Selenium proxy setup and authentication > Chrome's proxy setting takes no password, so this guide runs a small local forwarder that adds your netdash username and password, and points Selenium's browser at it. Source: https://netdash.io/developers/selenium · Updated 2026-10-09 A browser's proxy setting has no place for a password, so a Selenium proxy setup needs a second piece that supplies your netdash username and password. This guide uses a small local forwarder written in Python: Chrome sends its traffic to the forwarder on `127.0.0.1`, and the forwarder passes it on to the netdash gateway with your credentials added. It works with any Selenium 4 release and needs nothing beyond the Python standard library. All examples use the Scale plan on the Americas gateway, `scale-us.netdash.io:2000`; other plans differ only in host and port, listed in the [proxy format reference](https://netdash.io/docs/proxy-format). ## Why credentials don't go in --proxy-server Chromium's proxy documentation says Chrome doesn't use credentials embedded in proxy settings; proxy authentication goes through its normal credential flow instead. In our check with headless Chrome, a `--proxy-server` value of the form `http://user:pass@host:port` made Chrome reject the proxy setting with `net::ERR_NO_SUPPORTED_PROXIES`, and nothing reached the gateway. With the host and port alone and nobody answering the challenge, the gateway's `407 Proxy Authentication Required` ended the page load with `net::ERR_INVALID_AUTH_CREDENTIALS`. A visible browser may show a sign-in prompt instead, which a WebDriver session can't fill in. Selenium's Python bindings document a WebDriver BiDi handler for this, `driver.network.add_auth_handler()`. In our test (Selenium 4.36 on Python 3.9, Chrome 155) it didn't answer the gateway's `407`: the handler raised an error and the page load timed out. Until a version passes the same test, this guide uses the forwarder below, and we'll update the page when that changes. ## Selenium proxy authentication with a local forwarder Save the forwarder as `netdash_forwarder.py` next to your tests: _Checked against a local test proxy._ netdash_forwarder.py: ```python import asyncio import base64 import threading HOP_BY_HOP = ("proxy-authorization:", "proxy-connection:", "connection:", "keep-alive:") def start_forwarder(username: str, password: str, gateway: str = "scale-us.netdash.io:2000") -> str: """Start a local proxy on 127.0.0.1 that forwards to the netdash gateway with credentials. Returns its URL (for example http://127.0.0.1:53211) to pass to the browser. It adds the Proxy-Authorization header and passes everything else through: HTTPS stays a CONNECT tunnel, so the forwarder never sees page content. It runs until your script exits. """ host, port = gateway.rsplit(":", 1) token = base64.b64encode(f"{username}:{password}".encode()).decode() async def pipe(reader: asyncio.StreamReader, writer: asyncio.StreamWriter) -> None: try: while True: data = await reader.read(65536) if not data: break writer.write(data) await writer.drain() except (ConnectionError, asyncio.CancelledError): pass finally: writer.close() async def handle(client_reader: asyncio.StreamReader, client_writer: asyncio.StreamWriter) -> None: try: head = await client_reader.readuntil(b"\r\n\r\n") upstream = asyncio.open_connection(host, int(port)) upstream_reader, upstream_writer = await asyncio.wait_for(upstream, timeout=30) except (asyncio.IncompleteReadError, asyncio.LimitOverrunError, asyncio.TimeoutError, OSError): client_writer.close() return lines = head.decode("latin-1").split("\r\n") request_line = lines[0] headers = [h for h in lines[1:] if h and not h.lower().startswith(HOP_BY_HOP)] headers.insert(0, f"Proxy-Authorization: Basic {token}") if not request_line.startswith("CONNECT "): headers.append("Connection: close") # one request per connection, each with credentials upstream_writer.write(("\r\n".join([request_line, *headers]) + "\r\n\r\n").encode("latin-1")) await asyncio.gather(pipe(client_reader, upstream_writer), pipe(upstream_reader, client_writer)) loop = asyncio.new_event_loop() server = loop.run_until_complete(asyncio.start_server(handle, "127.0.0.1", 0)) threading.Thread(target=loop.run_forever, daemon=True).start() return f"http://127.0.0.1:{server.sockets[0].getsockname()[1]}" ``` Then start one forwarder per username and give Chrome its address: _Checked against a local test proxy._ chrome_proxy.py: ```python import os from selenium import webdriver from netdash_forwarder import start_forwarder username = f"{os.environ['NETDASH_USERNAME']}-cc-US" local_proxy = start_forwarder(username, os.environ["NETDASH_PASSWORD"], "scale-us.netdash.io:2000") options = webdriver.ChromeOptions() options.add_argument(f"--proxy-server={local_proxy}") driver = webdriver.Chrome(options=options) try: driver.set_page_load_timeout(30) driver.get("https://api.ipify.org?format=json") print(driver.find_element("tag name", "body").text) finally: driver.quit() ``` The page shows the exit IP the website saw, for example `{"ip":"203.0.113.10"}`. Here is what each part does: - `start_forwarder()` listens on a free port on `127.0.0.1` and returns its address, for example `http://127.0.0.1:53211`. - For every connection from the browser, it opens a connection to the gateway and adds a `Proxy-Authorization` header with your username and password. The gateway never has to send a `407` challenge. - HTTPS pages go through a CONNECT tunnel: the forwarder passes the encrypted bytes through and never sees page content, and certificates verify normally. - Plain-HTTP requests are sent one per connection, so each one carries the credentials. - The username carries the exit country (`-cc-US`), and the password is read from `NETDASH_PASSWORD`. > **Credentials** Keep `NETDASH_USERNAME` and `NETDASH_PASSWORD` in a `.env` file or your secret manager, never in source control or in a chat with an assistant. Don't log the username and password together. The forwarder accepts connections from your own machine only, but any program running on it could use the forwarder while your script runs. ## Exit countries and sessions The exit country and the sticky session are part of the username, and a forwarder always uses the username it was started with. Start one forwarder and one driver per country or per sticky session, and quit the driver to end the session: _Checked against a local test proxy._ netdash_driver.py: ```python import os import secrets from typing import Optional from selenium import webdriver from netdash_forwarder import start_forwarder def netdash_driver(country: str = "US", session_id: Optional[str] = None, ttl_minutes: int = 30, gateway: str = "scale-us.netdash.io:2000") -> webdriver.Chrome: """Chrome through netdash: rotating without session_id, sticky with one.""" cc = country.upper() cc = "GB" if cc == "UK" else cc username = f"{os.environ['NETDASH_USERNAME']}-cc-{cc}" if session_id is not None: username += f"-session-{session_id}-ttl-{ttl_minutes}" local_proxy = start_forwarder(username, os.environ["NETDASH_PASSWORD"], gateway) options = webdriver.ChromeOptions() options.add_argument(f"--proxy-server={local_proxy}") driver = webdriver.Chrome(options=options) driver.set_page_load_timeout(30) return driver # A sticky German session through the Europe gateway: one exit IP for the whole test driver = netdash_driver("DE", session_id=secrets.token_hex(4), gateway="scale-eu.netdash.io:2000") try: for _ in range(2): driver.get("https://api.ipify.org?format=json") print(driver.find_element("tag name", "body").text) # the same exit IP both times finally: driver.quit() ``` A sticky session keeps the same exit IP for the TTL you set while that IP stays available; the dashboard presets are 15 minutes, 30 minutes, 12 hours and 7 days. Use `GB` for the United Kingdom, and the `-eu` gateway for exits in Europe. [Rotating and sticky sessions](https://netdash.io/docs/sessions) explains how to choose. ## Other browsers, Selenium Grid and remote browsers The forwarder is an ordinary proxy without authentication, so other browsers can use it through their own proxy settings; we tested the examples with Chrome. The proxy applies where the browser runs. On Selenium Grid or another remote setup, the browser runs on the node, so the forwarder has to run on that node as well, and the node needs outbound access to the gateway's port (`2000` for Scale). Authentication is username and password only, so it doesn't matter which IP address the node connects from. ## Approaches we don't recommend > Don't put credentials in the proxy URL: Chrome rejects the setting or ignores the credentials. Don't disable certificate checks to make a proxy work; netdash tunnels HTTPS with CONNECT, so certificates verify normally. Avoid unmaintained wrappers that intercept and decrypt browser traffic with their own certificate in order to add proxy credentials. ## Errors and fixes | Symptom | Cause | Fix | |---|---|---| | `net::ERR_NO_SUPPORTED_PROXIES` | Credentials inside `--proxy-server` | Pass the forwarder's address, which has no credentials | | `net::ERR_INVALID_AUTH_CREDENTIALS` | Chrome points at the gateway directly, or the password or username parameters are wrong | Pass the forwarder's address to `--proxy-server`; check the password and the username parameters (`-cc-`, `-session-...-ttl-...`) | | `net::ERR_PROXY_CONNECTION_FAILED` or `net::ERR_CONNECTION_CLOSED` on every page | The forwarder can't reach the gateway: wrong host and port pair, or the network blocks the outbound port | Use the host and port from the endpoint table; allow outbound TCP to the gateway port | | `TimeoutException` from `driver.get()` | The page didn't load within the page load timeout | Check the gateway first with the ipify page, then raise the timeout for slow pages | | Wrong exit country, or the country doesn't change | One forwarder and driver reused across countries | Start a new forwarder and driver per country or session; use `GB`, not `UK` | > netdash forwards your traffic unchanged. It doesn't solve CAPTCHAs, render JavaScript or retry requests. Targeting is country-level. > Automate only sites and workflows you are permitted to use, respect each site's terms and robots.txt, and keep your request rate reasonable. Don't access content behind logins or paywalls without authorization, and test only systems you own or are authorized to test. netdash does not solve CAPTCHAs: if a site rate-limits or challenges your session, slow down, check its terms, or use its official API. Read [how we expect netdash to be used](https://netdash.io/responsible-use). ## Next steps Starting a new test suite? Playwright accepts the proxy username and password in its `proxy` option, for the whole browser or per context; see the [Playwright proxy setup](https://netdash.io/developers/playwright). The [Puppeteer proxy setup](https://netdash.io/developers/puppeteer) covers `page.authenticate`, and [browser automation](https://netdash.io/solutions/browser-automation) shows the workflows these tools are used for. Every other tool is in the [proxy integration guides](https://netdash.io/developers). If you haven't generated credentials yet, the [quickstart](https://netdash.io/docs/quickstart) walks you through it. - [Playwright proxy setup](https://netdash.io/developers/playwright): Proxy credentials per browser or per context, for Node.js and Python. - [Rotating and sticky sessions](https://netdash.io/docs/sessions): Session IDs, TTL presets and when to rotate instead. - [Localization testing](https://netdash.io/solutions/localization-testing): Language, currency and geo-redirects as visitors in each country see them. Stuck? See [troubleshooting](https://netdash.io/docs/troubleshooting) or email support with your username (without the password) and the error message. [Email support](mailto:support@netdash.io?subject=Proxy%20troubleshooting&body=Plan%3A%0AGateway%20host%3Aport%3A%0AUsername%20%28no%20password%29%3A%0ATime%20%28UTC%29%3A%0AWebsite%20domain%3A%0AError%20message%3A%0AClient%20and%20version%3A%0A) ## Sources - [Chromium docs: Proxy support in Chrome (credentials embedded in proxy settings are not used)](https://chromium.googlesource.com/chromium/src/+/HEAD/net/docs/proxy.md) (accessed 2026-10-09) - [Selenium docs: Browser options, proxy](https://www.selenium.dev/documentation/webdriver/drivers/options/#proxy) (accessed 2026-10-09) - [Selenium docs: WebDriver BiDi network features (authentication handlers)](https://www.selenium.dev/documentation/webdriver/bidi/network/) (accessed 2026-10-09) - [Selenium Python API: selenium.webdriver.common.bidi.network (add_auth_handler)](https://www.selenium.dev/selenium/docs/api/py/selenium_webdriver_common_bidi/selenium.webdriver.common.bidi.network.html) (accessed 2026-10-09) - [Python docs: asyncio streams (start_server, open_connection)](https://docs.python.org/3/library/asyncio-stream.html) (accessed 2026-10-09) - [Playwright docs: Network, HTTP proxy (per browser or per browser context, optional username and password)](https://playwright.dev/docs/network#http-proxy) (accessed 2026-10-09) --- # Playwright proxy setup and authentication > Pass `proxy: { server, username, password }` when you launch the browser or create a context. Put the netdash username, with its country and session parameters, in `username`, not in the server URL. Source: https://netdash.io/developers/playwright · Updated 2026-10-09 A Playwright proxy is a launch option or a context option, in the Node.js and Python APIs alike, and both forms accept a username and password. That is all netdash needs: no SDK, no extension, no local helper proxy. This page is one of the [proxy integration guides](https://netdash.io/developers) and covers the Playwright library itself. netdash is an HTTP proxy. HTTPS sites go through a CONNECT tunnel, so the `server` value always starts with `http://`, even when every page you open uses HTTPS. ## Quick start (Node.js) With Playwright and its Chromium build installed, this script opens a test page through the Americas gateway on the Scale plan and prints the exit IP: Playwright: ```javascript import { chromium } from "playwright"; const browser = await chromium.launch({ proxy: { server: "http://scale-us.netdash.io:2000", username: "USERNAME-cc-US", password: "PASSWORD", }, }); const page = await browser.newPage(); await page.goto("https://api.ipify.org?format=json"); console.log(await page.textContent("body")); await browser.close(); ``` _Syntax-checked._ Replace `USERNAME` and `PASSWORD` with the credentials from the dashboard (Residential → Generate), or read them from the environment as the examples below do. The output is JSON with the exit IP, for example `{"ip":"203.0.113.10"}`. No account yet? The [quickstart](https://netdash.io/docs/quickstart) goes from sign-up to a first proxied request. ## Playwright proxy authentication The `proxy` option has four fields, and the netdash values go in like this: | Field | netdash value | Notes | |---|---|---| | `server` | `http://scale-us.netdash.io:2000` | Scheme, host and port only. Use `scale-eu.netdash.io:2000` for exits in Europe. | | `username` | `USERNAME-cc-US` or `USERNAME-cc-US-session-a1b2c3d4-ttl-30` | Your username plus the exit country and, for a sticky session, a session ID and TTL in minutes. | | `password` | The proxy password from the dashboard | Not your sign-in password. The same for every residential and mobile plan. | | `bypass` | Optional, for example `localhost,127.0.0.1` | Hosts that bypass the proxy and connect directly, see below. | Playwright documents the credentials as separate `username` and `password` fields, so keep `server` to the scheme, host and port and don't write `user:pass@` into it. `USERNAME-cc-US-session-a1b2c3d4-ttl-30`: your username, then `-cc-` and the exit country (ISO code, GB for the UK), then for sticky sessions `-session-` and an ID (letters and digits) and `-ttl-` and the lifetime in minutes. Leave out the session part for rotating sessions. `httpCredentials` is a different option. It answers HTTP authentication from the website itself (a 401 response), and Playwright's docs note that without an origin it sends the credentials to any server that asks. netdash authenticates at the proxy (a 407 response), so use `proxy.username` and `proxy.password` for netdash and keep `httpCredentials` for sites that need it. Every host, port and username parameter is listed in the [proxy format reference](https://netdash.io/docs/proxy-format). Keep `NETDASH_USERNAME` and `NETDASH_PASSWORD` in a `.env` file or your secret manager, never in source control or in a chat with an assistant. ## One proxy per browser context A browser context is an isolated session with its own cookies and storage. Give each context its own `proxy` to run several exit countries or sessions in one browser. The script below opens two contexts through the Europe gateway: a sticky session in Germany and a rotating one in the United Kingdom. Use `GB` for the United Kingdom, not `UK`. contexts.mjs: ```js import { randomBytes } from "node:crypto"; import { chromium } from "playwright"; const { NETDASH_USERNAME, NETDASH_PASSWORD } = process.env; const server = "http://scale-eu.netdash.io:2000"; // Europe gateway, Scale plan const browser = await chromium.launch(); try { // Sticky: one session ID keeps one exit IP for the 30-minute TTL const sessionId = randomBytes(4).toString("hex"); // 8 characters, letters and digits const germany = await browser.newContext({ proxy: { server, username: `${NETDASH_USERNAME}-cc-DE-session-${sessionId}-ttl-30`, password: NETDASH_PASSWORD, }, }); // Rotating: no session parameters const unitedKingdom = await browser.newContext({ proxy: { server, username: `${NETDASH_USERNAME}-cc-GB`, password: NETDASH_PASSWORD }, }); for (const [country, context] of [["DE", germany], ["GB", unitedKingdom]]) { const page = await context.newPage(); await page.goto("https://api.ipify.org?format=json", { timeout: 30_000 }); console.log(country, await page.textContent("body")); await context.close(); } } finally { await browser.close(); } ``` _Syntax-checked._ Close a context when its task is done. Cookies set in one context never reach another, which keeps the two countries' sessions apart. ## Sticky sessions for multi-step flows Some flows span several pages: a language choice, a search, then the results pages. If the site ties that state to the visitor's IP address, keep the whole flow on one exit IP with a sticky session. Use one session ID for the context, and a new context with a new ID for the next run. Reusing a session ID keeps the same exit IP for the TTL while that IP stays available. The TTL is in minutes. The dashboard presets are 15 minutes, 30 minutes, 12 hours and 7 days (`-ttl-15`, `-ttl-30`, `-ttl-720`, `-ttl-10080`); pick one longer than the flow. The [sticky session syntax](https://netdash.io/docs/sessions) has the details. sticky_flow.py: ```python import asyncio import os import secrets from playwright.async_api import Page, async_playwright async def exit_ip(page: Page) -> str: await page.goto("https://api.ipify.org?format=json") return await page.text_content("body") async def run_flow(urls: list) -> None: session_id = secrets.token_hex(4) # 8 characters, letters and digits async with async_playwright() as p: browser = await p.chromium.launch() context = await browser.new_context(proxy={ "server": "http://scale-us.netdash.io:2000", "username": f"{os.environ['NETDASH_USERNAME']}-cc-US-session-{session_id}-ttl-30", "password": os.environ["NETDASH_PASSWORD"], }) try: page = await context.new_page() page.set_default_navigation_timeout(30_000) print("start:", await exit_ip(page)) for url in urls: await page.goto(url) print(url, await page.title()) await asyncio.sleep(2) # keep the pace gentle for the site print("end: ", await exit_ip(page)) # same session, same exit IP finally: await context.close() await browser.close() asyncio.run(run_flow(["https://example.com/", "https://example.com/about"])) ``` _Syntax-checked._ ## Python (sync API) The sync API takes the same dictionary. Set at launch, the proxy covers the whole browser; set on `new_context()`, it covers that context. launch.py: ```python import os from playwright.sync_api import sync_playwright with sync_playwright() as p: browser = p.chromium.launch(proxy={ "server": "http://scale-us.netdash.io:2000", "username": f"{os.environ['NETDASH_USERNAME']}-cc-US", "password": os.environ["NETDASH_PASSWORD"], }) try: page = browser.new_page() page.goto("https://api.ipify.org?format=json", timeout=30_000) print(page.text_content("body")) finally: browser.close() ``` _Syntax-checked._ ## Playwright Test configuration In Playwright Test, `proxy` is a `use` option, so you can set it once for the whole suite or per project. Projects per country run the same tests from three exit countries. Pair each with a matching `locale`, so the browser's language settings and the exit IP agree, which is what localization tests usually need. Single proxy: ```ts import { defineConfig } from "@playwright/test"; export default defineConfig({ use: { proxy: { server: "http://scale-us.netdash.io:2000", username: `${process.env.NETDASH_USERNAME}-cc-US`, password: process.env.NETDASH_PASSWORD, bypass: "localhost,127.0.0.1", }, navigationTimeout: 30_000, }, }); ``` Projects per country: ```ts import { defineConfig } from "@playwright/test"; // Exits in the Americas use the -us gateway, exits in Europe the -eu gateway. const markets = [ { country: "US", gateway: "scale-us.netdash.io:2000", locale: "en-US" }, { country: "DE", gateway: "scale-eu.netdash.io:2000", locale: "de-DE" }, { country: "GB", gateway: "scale-eu.netdash.io:2000", locale: "en-GB" }, ]; export default defineConfig({ use: { navigationTimeout: 30_000 }, projects: markets.map(({ country, gateway, locale }) => ({ name: country, use: { locale, proxy: { server: `http://${gateway}`, username: `${process.env.NETDASH_USERNAME}-cc-${country}`, password: process.env.NETDASH_PASSWORD, bypass: "localhost,127.0.0.1", }, }, })), }); ``` _Syntax-checked._ Run one market with `npx playwright test --project=DE`, or all three with `npx playwright test`. ## Keep local services off the proxy The `bypass` field lists hosts that bypass the proxy and connect directly, separated by commas. Add the app under test on `localhost`, a staging host you operate, or anything else that doesn't need an exit country: the gateway can't reach `localhost` or hosts on your private network, and direct traffic doesn't count against your balance. For example: `bypass: "localhost,127.0.0.1,staging.example.com"`. ## Using Playwright from an AI agent Playwright MCP lets coding agents drive a Playwright browser. Its `--proxy-server` flag takes only a server address, so the credentials go in a config file: see [Claude Code with Playwright MCP](https://netdash.io/developers/claude-code#claude-code-with-a-playwright-mcp-proxy). For Browser Use, the OpenAI Agents SDK and LangChain, see the [agent frameworks guide](https://netdash.io/developers/agent-frameworks). ## Errors and fixes | Symptom | Cause | Fix | |---|---|---| | `net::ERR_PROXY_CONNECTION_FAILED` | Wrong host or port, or a firewall blocks outbound traffic to the gateway port | Copy the endpoint from the [proxy format reference](https://netdash.io/docs/proxy-format) and allow outbound TCP to the port (2000 for Scale) | | `net::ERR_TUNNEL_CONNECTION_FAILED`, or a 407 | Credentials written into `server`, a wrong password, or a malformed username | Move the credentials to `username` and `password`; check the `-cc-` and session parameters | | Navigation timeout | No timeout set, or a page that takes longer than the timeout | Set `timeout` on `goto()` or `navigationTimeout`; check the gateway region too: pair `US` with `-us` and `DE` or `GB` with `-eu` | | Wrong exit country | A context reused for another country, or `UK` instead of `GB` | Create one context per country; use `GB` | | Requests to a local or staging server fail in tests | They went through the proxy, and the gateway can't reach that host | Add the host to `bypass`, so its traffic skips the proxy | > netdash forwards your traffic unchanged. It doesn't solve CAPTCHAs, render JavaScript or retry requests. Targeting is country-level. > **Use it responsibly** Respect robots.txt and each site's terms, keep request rates reasonable and back off when a site slows you down. Don't collect personal data without a lawful basis or open content behind logins or paywalls without authorization. netdash doesn't solve CAPTCHAs or bot challenges, and your scripts shouldn't try to get past them either: if a site rate-limits or challenges your requests, slow down, check its terms, or use its official API. Read [how we expect netdash to be used](https://netdash.io/responsible-use). ## Next steps - [Puppeteer proxy setup](https://netdash.io/developers/puppeteer): The same setup for Puppeteer: --proxy-server plus page.authenticate(). - [Country targeting](https://netdash.io/docs/targeting): ISO codes, GB for the United Kingdom, and which gateway to pair with each exit. - [Localization and QA](https://netdash.io/solutions/localization-testing): Check how your site looks and behaves for visitors in other countries. Also useful: the [Selenium guide](https://netdash.io/developers/selenium), [browser automation with netdash](https://netdash.io/solutions/browser-automation) and [rotating and sticky sessions](https://netdash.io/docs/sessions). Stuck? See [troubleshooting](https://netdash.io/docs/troubleshooting) or email support with your username (without the password) and the error message. [Email support](mailto:support@netdash.io?subject=Proxy%20troubleshooting&body=Plan%3A%0AGateway%20host%3Aport%3A%0AUsername%20%28no%20password%29%3A%0ATime%20%28UTC%29%3A%0AWebsite%20domain%3A%0AError%20message%3A%0AClient%20and%20version%3A%0A) ## Sources - [Playwright docs: Network, HTTP proxy](https://playwright.dev/docs/network#http-proxy) (accessed 2026-10-09) - [Playwright API: browserType.launch(), proxy option](https://playwright.dev/docs/api/class-browsertype#browser-type-launch-option-proxy) (accessed 2026-10-09) - [Playwright API: browser.newContext(), proxy option](https://playwright.dev/docs/api/class-browser#browser-new-context-option-proxy) (accessed 2026-10-09) - [Playwright API: browser.newContext(), httpCredentials option](https://playwright.dev/docs/api/class-browser#browser-new-context-option-http-credentials) (accessed 2026-10-09) - [Playwright for Python: Network, HTTP proxy](https://playwright.dev/python/docs/network#http-proxy) (accessed 2026-10-09) - [Playwright for Python API: browser.new_context(), proxy](https://playwright.dev/python/docs/api/class-browser#browser-new-context) (accessed 2026-10-09) - [Playwright Test: use options (proxy, projects)](https://playwright.dev/docs/test-use-options) (accessed 2026-10-09) --- # Puppeteer proxy setup and authentication > Launch Chrome with `--proxy-server=http://scale-us.netdash.io:2000` and call `page.authenticate()` with your netdash username and password before the first navigation. Source: https://netdash.io/developers/puppeteer · Updated 2026-10-09 Puppeteer has no proxy field in its launch options, so a Puppeteer proxy setup has two parts. You pass Chrome's `--proxy-server` switch in `args`, with the scheme, host and port only, and you answer the gateway's authentication challenge with `page.authenticate()`. Call it on every page, before that page's first `goto()`. This page is one of the [proxy integration guides](https://netdash.io/developers). netdash is an HTTP proxy. HTTPS sites go through a CONNECT tunnel, so the switch always uses `http://`, even when the pages you open use HTTPS. ## Quick start: --proxy-server plus page.authenticate launch.mjs: ```js import puppeteer from "puppeteer"; const { NETDASH_USERNAME, NETDASH_PASSWORD } = process.env; const browser = await puppeteer.launch({ args: ["--proxy-server=http://scale-us.netdash.io:2000"], }); try { const page = await browser.newPage(); // Authenticate before the first goto(), or the gateway answers with a 407 await page.authenticate({ username: `${NETDASH_USERNAME}-cc-US`, password: NETDASH_PASSWORD }); await page.goto("https://api.ipify.org?format=json", { timeout: 30_000 }); console.log(await page.evaluate(() => document.body.innerText)); } finally { await browser.close(); } ``` _Checked against a local test proxy._ Run it as an ES module (`.mjs`, or `"type": "module"` in `package.json`) with `NETDASH_USERNAME` and `NETDASH_PASSWORD` set. It prints the exit IP as JSON, for example `{"ip":"203.0.113.10"}`. Keep both values in a `.env` file or your secret manager, never in source control or in a chat with an assistant. If you don't have credentials yet, start with the [quickstart](https://netdash.io/docs/quickstart). ## Puppeteer proxy authentication Puppeteer's `BrowserContextOptions` reference describes the split: `proxyServer` is the server with an optional port, and the username and password are set with `page.authenticate()`. The `--proxy-server` switch follows the same pattern: it carries `http://scale-us.netdash.io:2000` and nothing else, and the credentials go to the page. The username carries the netdash parameters: | Mode | `username` | Exit IP | |---|---|---| | Rotating | `USERNAME-cc-US` | Not pinned; the gateway assigns exit IPs | | Sticky | `USERNAME-cc-DE-session-a1b2c3d4-ttl-30` | Same session ID, same exit IP for the 30-minute TTL while that IP stays available | The password is the proxy password shown in the dashboard (not your sign-in password), the same for every residential and mobile plan. The [proxy format reference](https://netdash.io/docs/proxy-format) lists every host, port and parameter. Two details from Puppeteer's `page.authenticate()` reference matter in practice. To answer the challenge, Puppeteer turns on request interception behind the scenes, which its docs say might affect performance. And passing `null` turns authentication off again for that page. ## A proxy per browser context `browser.createBrowserContext()` takes a `proxyServer` (and a `proxyBypassList`) for that context only. One browser can then run contexts with different exit countries or sessions, and each context keeps its own cookies and cache. Credentials still come from `page.authenticate()` on every page in the context. This example opens a context with a sticky session in Germany through the Europe gateway. Puppeteer's docs don't say whether a context proxy also needs a launch-level `--proxy-server`; in our check (Puppeteer 25.13, Chrome 155) it didn't: a browser launched without the switch sent the context's requests and HTTPS tunnels to the gateway with the page's credentials. context.mjs: ```js import { randomBytes } from "node:crypto"; import puppeteer from "puppeteer"; const { NETDASH_USERNAME, NETDASH_PASSWORD } = process.env; const sessionId = randomBytes(4).toString("hex"); // 8 characters, letters and digits const browser = await puppeteer.launch(); try { // Its own proxy server, cookies and cache const context = await browser.createBrowserContext({ proxyServer: "http://scale-eu.netdash.io:2000", }); const page = await context.newPage(); await page.authenticate({ username: `${NETDASH_USERNAME}-cc-DE-session-${sessionId}-ttl-30`, password: NETDASH_PASSWORD, }); page.setDefaultNavigationTimeout(30_000); await page.goto("https://api.ipify.org?format=json"); console.log(await page.evaluate(() => document.body.innerText)); await context.close(); } finally { await browser.close(); } ``` _Checked against a local test proxy._ Use `GB` for the United Kingdom, not `UK`, and pair exits in Europe with the `-eu` gateway. A new session ID starts a new sticky session; the [sticky session syntax](https://netdash.io/docs/sessions) explains TTLs and the dashboard presets (15 minutes, 30 minutes, 12 hours, 7 days). ## New tabs and popups Credentials belong to a page, not to the browser. A tab opened by `window.open()` or by a link with `target="_blank"` is a new page without them. Puppeteer emits `targetcreated` on the browser for every new target in every context, and `target.page()` returns the page, or `null` for targets that aren't pages. Authenticate each new page there, with the same username, so popups stay in the same session as the page that opened them. new-pages.mjs: ```js import puppeteer from "puppeteer"; const { NETDASH_USERNAME, NETDASH_PASSWORD } = process.env; const credentials = { username: `${NETDASH_USERNAME}-cc-US`, password: NETDASH_PASSWORD }; const browser = await puppeteer.launch({ args: ["--proxy-server=http://scale-us.netdash.io:2000"], }); // Register before anything opens new pages browser.on("targetcreated", async (target) => { const page = await target.page(); // null for workers and other non-page targets if (page) await page.authenticate(credentials); }); try { const page = await browser.newPage(); await page.authenticate(credentials); await page.goto("https://api.ipify.org?format=json", { timeout: 30_000 }); console.log(await page.evaluate(() => document.body.innerText)); } finally { await browser.close(); } ``` _Checked against a local test proxy._ A new page can start loading before the handler has run. If its first request fails with an authentication error, navigate it again after `authenticate()` has resolved. ## Errors and fixes | Symptom | Cause | Fix | |---|---|---| | `net::ERR_INVALID_AUTH_CREDENTIALS`, or a 407 | `authenticate()` called after `goto()`, a new tab without credentials, credentials written into `--proxy-server`, or a wrong password | Authenticate every page before its first navigation; keep the switch to `http://host:port`; copy the password from the dashboard | | `net::ERR_PROXY_CONNECTION_FAILED` | Wrong host or port, or a firewall blocks outbound traffic to the gateway port | Copy the endpoint from the [proxy format reference](https://netdash.io/docs/proxy-format); allow outbound TCP to the port (2000 for Scale) | | `net::ERR_TUNNEL_CONNECTION_FAILED` | The tunnel through the gateway couldn't be established. Start with the credentials: a wrong password, or a username outside the documented syntax (missing `-cc-`, `UK` instead of `GB`, a hyphen in the session ID) | Send the same username and password through curl, as the [troubleshooting guide](https://netdash.io/docs/troubleshooting) shows; if curl succeeds, check the website and the timeout | | Wrong exit country | A page authenticated with another country's username, or `UK` instead of `GB` | One username per context or task; use `GB` | | Pages got slower after adding `authenticate()` | Puppeteer answers the challenge with request interception, which its docs say might affect performance | Expected; don't add interception handlers you don't need | > netdash forwards your traffic unchanged. It doesn't solve CAPTCHAs, render JavaScript or retry requests. Targeting is country-level. > **Use it responsibly** Respect robots.txt and each site's terms, keep request rates reasonable and back off when a site slows you down. Don't collect personal data without a lawful basis or open content behind logins or paywalls without authorization. netdash doesn't solve CAPTCHAs or bot challenges, and your scripts shouldn't try to get past them either: if a site rate-limits or challenges your requests, slow down, check its terms, or use its official API. Read [how we expect netdash to be used](https://netdash.io/responsible-use). ## Next steps - [Playwright proxy setup](https://netdash.io/developers/playwright): Playwright takes username and password in its proxy option, per browser or per context. - [Troubleshooting](https://netdash.io/docs/troubleshooting): 407 errors, connection failures, timeouts and the wrong exit country, with the cause of each. - [Ad verification](https://netdash.io/solutions/ad-verification): Check how ads and landing pages render for visitors in other countries. Also useful: the [Node.js guide](https://netdash.io/developers/nodejs) for HTTP clients without a browser, [browser automation with netdash](https://netdash.io/solutions/browser-automation) and [rotating and sticky sessions](https://netdash.io/docs/sessions). Stuck? See [troubleshooting](https://netdash.io/docs/troubleshooting) or email support with your username (without the password) and the error message. [Email support](mailto:support@netdash.io?subject=Proxy%20troubleshooting&body=Plan%3A%0AGateway%20host%3Aport%3A%0AUsername%20%28no%20password%29%3A%0ATime%20%28UTC%29%3A%0AWebsite%20domain%3A%0AError%20message%3A%0AClient%20and%20version%3A%0A) ## Sources - [Puppeteer API: Page.authenticate()](https://pptr.dev/api/puppeteer.page.authenticate) (accessed 2026-10-09) - [Puppeteer API: BrowserContextOptions (proxyServer, proxyBypassList)](https://pptr.dev/api/puppeteer.browsercontextoptions) (accessed 2026-10-09) - [Puppeteer API: Browser.createBrowserContext()](https://pptr.dev/api/puppeteer.browser.createbrowsercontext) (accessed 2026-10-09) - [Puppeteer API: LaunchOptions (args)](https://pptr.dev/api/puppeteer.launchoptions) (accessed 2026-10-09) - [Puppeteer API: BrowserEvent (targetcreated)](https://pptr.dev/api/puppeteer.browserevent) (accessed 2026-10-09) - [Puppeteer API: Target (page(), type())](https://pptr.dev/api/puppeteer.target) (accessed 2026-10-09) --- # Use proxies with Claude Code and Playwright MCP > Let Claude Code wire netdash into your app's HTTP clients and give its Playwright MCP browser an authenticated proxy. Don't export `HTTPS_PROXY` in the shell that runs Claude Code: Claude Code reads it for its own API traffic. Source: https://netdash.io/developers/claude-code · Updated 2026-10-09 This guide covers proxies for the code and the browser that Claude Code builds and drives: the HTTP clients in your application, and the browser Claude Code controls through Playwright MCP. If you need Claude Code itself to reach the internet through a corporate proxy, that is a different setup, described in Anthropic's [network configuration docs](https://code.claude.com/docs/en/corporate-proxy). For the architecture and the choice of plan, see [proxies for AI agents](https://netdash.io/solutions/ai-agents). netdash is a standard HTTP proxy with username and password authentication, so there is no SDK or plugin to install. Claude Code only needs to know where the credentials live and which clients should use them. ## Which traffic should use the proxy _In a Claude Code project, the coding agent talks to its model API directly; your application's HTTP client and the Playwright MCP browser use the netdash proxy, configured per client. Do not export HTTPS_PROXY in the shell that runs the coding agent._ | Traffic | Through netdash? | Where it's configured | |---|---|---| | Claude Code to its model API | No | Leave Claude Code's own network settings alone | | Your application's HTTP client (requests, httpx, undici, net/http) | Yes | In code, from `NETDASH_USERNAME` and `NETDASH_PASSWORD` | | The Playwright MCP browser | Yes | In a Playwright MCP config file (`browser.launchOptions.proxy`) | | Package installs, git, downloads of MCP servers | No | Nothing to do, as long as no proxy variable is exported | ## Don't export HTTPS_PROXY for Claude Code > **Keep netdash out of Claude Code's proxy variables** Claude Code reads `HTTPS_PROXY` and `HTTP_PROXY` (and the lowercase forms) for its own traffic, from your shell and from the `env` block in its settings files. A netdash URL there sends Claude Code's model requests through netdash and meters them against your balance. Claude Code uses the first of these variables that is set, in the order `https_proxy`, `HTTPS_PROXY`, `http_proxy`, `HTTP_PROXY`. It reads shell variables once at startup, so a running session doesn't pick up later changes. Commands Claude Code runs and the stdio MCP servers it starts inherit its environment, so every one of them that honors proxy variables would send its traffic through netdash as well. A global `HTTPS_PROXY` applies to every request the process makes, including calls to model APIs and package registries. Those requests would go through netdash and count against your balance. Set the proxy on the client that fetches websites instead. To check, run `/status` in Claude Code. The **Proxy** row shows the active proxy URL. If it shows a netdash host, unset the variable or remove it from the settings `env` block, then restart Claude Code. ## Keep credentials in .env Put the credentials in a `.env` file that git ignores (add `.env` to `.gitignore`), and commit a `.env.example` with sample values so Claude Code and your teammates know the variable names. Then add deny rules to `.claude/settings.json`, so Claude Code's file tools don't open `.env` itself. .env.example: ```dotenv # Copy to .env (gitignored) and fill in your credentials from the dashboard NETDASH_USERNAME=USERNAME NETDASH_PASSWORD=PASSWORD ``` .claude/settings.json: ```json { "permissions": { "deny": [ "Read(./.env)", "Read(./.env.*)", "Read(!.env.example)" ] } } ``` _Syntax-checked._ What these rules do, according to Claude Code's permissions docs: - A `Read` deny rule covers Claude Code's built-in file tools and the file commands it recognizes in the shell, such as `cat` and `head`. - It also blocks editing or creating that file, so you create `.env` yourself and type the password there, not into the chat. - The `!` rule carves `.env.example` out of the rule before it, so Claude Code can still read and update the template. - The rules don't stop a program from reading the file. Your script that loads `.env` at run time keeps working, which is what you want. For an operating-system boundary around commands, Claude Code has a sandbox. Load `.env` when the program starts instead of exporting the values in the shell that runs Claude Code: run Node.js scripts with `node --env-file=.env` (Node.js 20.6 and later), and call `load_dotenv()` from the python-dotenv package at the top of a Python script. Neither overrides a variable that is already set in the environment. Keep `NETDASH_USERNAME` and `NETDASH_PASSWORD` in a `.env` file or your secret manager, never in source control or in a chat with an assistant. ## Setup prompt Paste this prompt into Claude Code, or into another coding agent, inside your project. It tells the agent to set the proxy on each client instead of exporting `HTTPS_PROXY`, read the credentials from the environment, set timeouts, report a 407 with a clear error, keep TLS verification on, add a script that prints the exit IP, stop and report when a site challenges or rate-limits the client, and only access sites the project is permitted to use. The prompt links the [proxy format reference](https://netdash.io/docs/proxy-format) in Markdown, so the agent can read the exact syntax. Setup prompt for your coding agent: ```text Add netdash proxy support to this project. Facts (from https://netdash.io/docs/proxy-format.md): - netdash is a standard HTTP proxy (HTTPS sites go through CONNECT). No SDK. - Gateways: Solve plan: solve-{us|eu}.netdash.io:1000; Scale plan: scale-{us|eu}.netdash.io:2000; Opus plan: opus-{us|eu}.netdash.io:3000; Mobile: mobile-{us|eu}.netdash.io:4000. Use -us for exits in the Americas, -eu for Europe. - Username: NETDASH_USERNAME-cc-{COUNTRY} for rotating sessions, or NETDASH_USERNAME-cc-{COUNTRY}-session-{ID}-ttl-{MINUTES} for sticky sessions. COUNTRY is ISO 3166-1 alpha-2 uppercase (GB, not UK). ID is letters and digits only. - Password: NETDASH_PASSWORD (the proxy password from the dashboard, not the sign-in password). Requirements: - Read NETDASH_USERNAME and NETDASH_PASSWORD from the environment (.env, never commit them). Do not hardcode credentials. - Configure the proxy on the HTTP client or browser explicitly. Do not export HTTP_PROXY/HTTPS_PROXY globally in the shell that runs the coding agent, and do not add them to the coding agent's settings env either. - Python requests: set both "http" and "https" in proxies. httpx: use proxy=. Node.js: use undici ProxyAgent with undici fetch. Playwright/Puppeteer: pass the server separately from username and password. Selenium/Chrome takes no proxy credentials: use the local forwarder from https://netdash.io/developers/selenium.md. - Set timeouts, handle HTTP 407 (wrong credentials) with a clear error, and keep TLS verification on. - Add a small check that prints the exit IP from https://api.ipify.org?format=json through the proxy. - netdash does not solve CAPTCHAs. If a site shows a challenge or rate-limits the client, stop and report it instead of retrying. - Only access sites and data the project is permitted to use, respect robots.txt and rate limits. ``` No account or credentials yet? The [quickstart](https://netdash.io/docs/quickstart) covers sign-up, buying a plan and where the dashboard shows your username. ## What Claude Code should produce Whatever the stack, the result should look like the examples in the language guides: the proxy set on one client, the credentials read from the environment, and a check that prints the exit IP. The helpers below are the same ones the [Python requests proxy setup](https://netdash.io/developers/python) and [Node.js proxy setup](https://netdash.io/developers/nodejs) guides use. Ask Claude Code to run the check after it wires the proxy in, with the variables loaded from `.env` (for example `node --env-file=.env check-proxy.mjs`). check_proxy.py: ```python """Print the exit IP that netdash assigns, to confirm the proxy setup.""" import os import sys from typing import Optional import requests NETDASH_USERNAME = os.environ["NETDASH_USERNAME"] NETDASH_PASSWORD = os.environ["NETDASH_PASSWORD"] def proxy_url(country: str = "US", session_id: Optional[str] = None, ttl_minutes: int = 30, gateway: str = "scale-us.netdash.io:2000") -> str: """netdash proxy URL: rotating without session_id, sticky with one.""" cc = country.upper() cc = "GB" if cc == "UK" else cc username = f"{NETDASH_USERNAME}-cc-{cc}" if session_id is not None: username += f"-session-{session_id}-ttl-{ttl_minutes}" return f"http://{username}:{NETDASH_PASSWORD}@{gateway}" def main() -> int: proxy = proxy_url("US") try: response = requests.get("https://api.ipify.org?format=json", proxies={"http": proxy, "https": proxy}, timeout=30) except requests.exceptions.ProxyError as exc: if "407" in str(exc): print("netdash answered 407: check NETDASH_USERNAME, NETDASH_PASSWORD and -cc-", file=sys.stderr) else: print("Could not connect through the netdash gateway", file=sys.stderr) return 1 if response.status_code == 407: print("netdash answered 407: check NETDASH_USERNAME, NETDASH_PASSWORD and -cc-", file=sys.stderr) return 1 response.raise_for_status() print("Exit IP:", response.json()["ip"]) return 0 if __name__ == "__main__": sys.exit(main()) ``` check-proxy.mjs: ```js import { ProxyAgent, fetch } from "undici"; const { NETDASH_USERNAME, NETDASH_PASSWORD } = process.env; export function netdashAgent({ country = "US", sessionId, ttl = 30, gateway = "http://scale-us.netdash.io:2000" } = {}) { const cc = country.toUpperCase() === "UK" ? "GB" : country.toUpperCase(); let username = `${NETDASH_USERNAME}-cc-${cc}`; if (sessionId) username += `-session-${sessionId}-ttl-${ttl}`; return new ProxyAgent({ uri: gateway, token: `Basic ${Buffer.from(`${username}:${NETDASH_PASSWORD}`).toString("base64")}`, }); } const agent = netdashAgent({ country: "US" }); try { const res = await fetch("https://api.ipify.org?format=json", { dispatcher: agent, signal: AbortSignal.timeout(30_000), }); if (!res.ok) throw new Error(`HTTP ${res.status}`); console.log("Exit IP:", (await res.json()).ip); } catch (err) { const detail = String(err.cause?.message ?? err.message); console.error(detail.includes("407") ? "netdash answered 407: check NETDASH_USERNAME, NETDASH_PASSWORD and -cc-" : `Request failed: ${detail}`); process.exitCode = 1; } finally { await agent.close(); } ``` _Checked against a local test proxy._ Neither script prints the password. The Python script puts the credentials into a proxy URL, so if your password contains characters such as `@`, `:` or `/`, percent-encode it there with `urllib.parse.quote(value, safe="")`. The Node.js script sends them in a header and needs no encoding. ## Claude Code with a Playwright MCP proxy Playwright MCP, from Microsoft's Playwright project, is an MCP server that gives Claude Code a Playwright browser to drive. Its `--proxy-server` option takes a server address, and the README documents no username or password option next to it. The credentials therefore go in a Playwright MCP config file. The README documents `browser.launchOptions` in that file as the options passed to Playwright's browser launch, and Playwright's `proxy` launch option has `username` and `password` fields. 1. Save the config file outside the repository, for example as `~/.config/netdash/playwright-mcp.json`, and make it readable only by you. 2. Add the server at local scope with `claude mcp add`: it is stored in `~/.claude.json`, not in the repository. Everything after `--` is passed to the server unchanged. 3. For a team, commit a `.mcp.json` instead and let each person point to their own config file through `NETDASH_PLAYWRIGHT_MCP_CONFIG`. Claude Code expands `${VAR}` in `command` and `args`, and asks each person to approve project servers from `.mcp.json` before using them. playwright-mcp.json: ```json { "browser": { "launchOptions": { "proxy": { "server": "http://scale-us.netdash.io:2000", "username": "USERNAME-cc-US-session-a1b2c3d4-ttl-30", "password": "PASSWORD" } } } } ``` claude mcp add: ```bash # Save the file above outside the repository, readable only by you chmod 600 ~/.config/netdash/playwright-mcp.json # Local scope: stored in ~/.claude.json, not in the repository claude mcp add --scope local playwright -- \ npx @playwright/mcp@latest --isolated --config "$HOME/.config/netdash/playwright-mcp.json" ``` .mcp.json: ```json { "mcpServers": { "playwright": { "command": "npx", "args": ["@playwright/mcp@latest", "--isolated", "--config", "${NETDASH_PLAYWRIGHT_MCP_CONFIG}"] } } } ``` _Syntax-checked._ We checked this config file with Playwright MCP 0.0.83 and Chrome 155 against a local test gateway: with `--isolated` and with a persistent profile, the gateway received the username and password on plain-HTTP requests and on the CONNECT for HTTPS pages. `--isolated` keeps the browser profile in memory instead of on disk. By default Playwright MCP keeps a persistent profile, so cookies and sign-ins would carry over from one task to the next. Don't combine the config file with `--proxy-server`: keep the whole proxy definition in one place. To check the setup, start a new Claude Code session and ask it to open `https://api.ipify.org?format=json` with the Playwright browser and report the IP address. It should be a netdash exit IP, not your own address. ## Sticky sessions for multi-step browser tasks Playwright MCP launches its browser with the username from the config file. For a task that spans several pages, put a sticky session in that username, as in the example: `USERNAME-cc-US-session-a1b2c3d4-ttl-30`. Reusing a session ID keeps the same exit IP for the TTL while that IP stays available. The TTL is in minutes, and the dashboard presets are 15 minutes, 30 minutes, 12 hours and 7 days. For a new session, change the session ID in the file (letters and digits only, no hyphens) and restart the MCP server, for example by restarting Claude Code. For rotating sessions, leave out the session part (`USERNAME-cc-US`) and the gateway assigns exit IPs. A browser keeps connections open, and requests sent through one HTTPS tunnel share that tunnel's exit IP, so if consecutive pages must share an exit IP, use a sticky session. The [sticky session syntax](https://netdash.io/docs/sessions) has the details. ## Cursor and other coding assistants The same rule applies to other coding assistants: give the proxy to the clients and the browser they build or drive, not to the assistant's own network settings. This guide doesn't cover an assistant's own proxy configuration. Cursor reads MCP servers from `.cursor/mcp.json` in the project, or from `~/.cursor/mcp.json` for all projects, and resolves `${env:NAME}` in `command`, `args` and `env`. The Playwright MCP entry with the same config file looks like this: .cursor/mcp.json: ```json { "mcpServers": { "playwright": { "command": "npx", "args": ["@playwright/mcp@latest", "--isolated", "--config", "${env:NETDASH_PLAYWRIGHT_MCP_CONFIG}"] } } } ``` _Syntax-checked._ Other MCP clients that the Playwright MCP README lists, such as VS Code and Windsurf, start the same command with the same arguments. The file location and the top-level key differ by client, so check its MCP documentation; [Playwright MCP with any MCP client](https://netdash.io/developers/agent-frameworks#playwright-mcp) shows the generic configuration. The setup prompt above works with any coding agent. ## When a site pushes back netdash doesn't solve CAPTCHAs, render JavaScript or retry requests: a challenge page reaches the agent's browser exactly as the site sent it. An agent that keeps retrying a challenge only adds load on the site and uses GB. Tell the agent to stop and report instead. The setup prompt includes that rule; add it to your project's `CLAUDE.md` as well if the agent browses on its own. > netdash forwards your traffic unchanged. It doesn't solve CAPTCHAs, render JavaScript or retry requests. Targeting is country-level. > **Use it responsibly** Respect robots.txt and each site's terms, keep request rates reasonable and back off when a site slows you down. Don't collect personal data without a lawful basis or open content behind logins or paywalls without authorization. netdash doesn't solve CAPTCHAs or bot challenges, and your agent shouldn't try to get past them either: if a site rate-limits or challenges your requests, slow down, check its terms, or use its official API. Read [how we expect netdash to be used](https://netdash.io/responsible-use). ## Errors and fixes | Symptom | Cause | Fix | |---|---|---| | Claude Code can't reach its API, or its traffic shows up on your netdash balance | `HTTPS_PROXY` or `HTTP_PROXY` holds a netdash URL, in the shell or in the settings `env` block | Unset it, restart Claude Code and check the **Proxy** row in `/status` | | Claude Code stops at launch with an error naming a proxy variable | The proxy URL can't be parsed, for example because it has no `http://` scheme | Fix or unset the variable | | The MCP browser shows a 407 or a sign-in prompt for the proxy | Only `--proxy-server` is set, so the browser has no credentials | Use the config file with `username` and `password` | | The Playwright MCP server from `.mcp.json` fails to start | `NETDASH_PLAYWRIGHT_MCP_CONFIG` isn't set where Claude Code runs, so the text `${NETDASH_PLAYWRIGHT_MCP_CONFIG}` is passed on unexpanded; `claude mcp list` shows a missing-variable warning | Set the variable before starting Claude Code | | Cookies or sign-ins carry over between tasks | Playwright MCP uses its persistent profile | Start it with `--isolated` | | The exit-IP check works in your terminal but fails when Claude Code runs it | Claude Code's Bash sandbox is on (it's off by default). Sandboxed commands reach the network only through its own local proxy, so a client that connects to the gateway directly can't connect | Run the check yourself, or exclude that command from the sandbox with `sandbox.excludedCommands` | | The agent keeps retrying a challenge page | Its instructions have no stop rule | Add "stop and report on challenges" to the prompt or `CLAUDE.md` | ## Next steps - [Proxies for AI agents](https://netdash.io/solutions/ai-agents): How netdash fits into agent architectures, and which plan and session settings to start with. - [Agent frameworks](https://netdash.io/developers/agent-frameworks): Playwright MCP with any MCP client, plus Browser Use, the OpenAI Agents SDK and LangChain. - [Playwright proxy setup](https://netdash.io/developers/playwright): Per-browser and per-context proxies in Playwright for Node.js and Python. Also useful: [cloud browser proxy setup](https://netdash.io/developers/cloud-browsers), [rotating and sticky sessions](https://netdash.io/docs/sessions), the agent-readable docs index at [/llms.txt](https://netdash.io/llms.txt), and [how we expect netdash to be used](https://netdash.io/responsible-use). Stuck? See [troubleshooting](https://netdash.io/docs/troubleshooting) or email support with your username (without the password) and the error message. [Email support](mailto:support@netdash.io?subject=Proxy%20troubleshooting&body=Plan%3A%0AGateway%20host%3Aport%3A%0AUsername%20%28no%20password%29%3A%0ATime%20%28UTC%29%3A%0AWebsite%20domain%3A%0AError%20message%3A%0AClient%20and%20version%3A%0A) ## Sources - [Claude Code docs: Enterprise network configuration (proxy variables, order, startup check, /status)](https://code.claude.com/docs/en/corporate-proxy) (accessed 2026-10-09) - [Claude Code docs: Environment variables (settings env, subprocess environment)](https://code.claude.com/docs/en/env-vars) (accessed 2026-10-09) - [Claude Code docs: Settings files and precedence (deny rules for .env)](https://code.claude.com/docs/en/settings) (accessed 2026-10-09) - [Claude Code docs: Configure permissions (Read deny rules, ! carve-outs, limits)](https://code.claude.com/docs/en/permissions) (accessed 2026-10-09) - [Claude Code docs: MCP (claude mcp add, scopes, .mcp.json, variable expansion)](https://code.claude.com/docs/en/mcp) (accessed 2026-10-09) - [Claude Code docs: Sandboxing (network access for sandboxed commands, excludedCommands)](https://code.claude.com/docs/en/sandboxing) (accessed 2026-10-09) - [Playwright MCP README (--proxy-server, --config, --isolated, configuration file schema)](https://github.com/microsoft/playwright-mcp) (accessed 2026-10-09) - [Playwright API: browserType.launch(), proxy option](https://playwright.dev/docs/api/class-browsertype#browser-type-launch-option-proxy) (accessed 2026-10-09) - [Node.js docs: Command-line options, --env-file](https://nodejs.org/api/cli.html#--env-filefile) (accessed 2026-10-09) - [python-dotenv README (load_dotenv)](https://github.com/theskumar/python-dotenv) (accessed 2026-10-09) - [Cursor docs: Model Context Protocol (mcp.json locations, config interpolation)](https://cursor.com/docs/context/mcp) (accessed 2026-10-09) --- # Proxy setup for AI agent frameworks > Give the agent's browser or HTTP tool a netdash proxy, and leave the model API client alone. Each framework below takes the proxy in a different place. Source: https://netdash.io/developers/agent-frameworks · Updated 2026-10-09 In an AI agent framework, proxy setup starts with one question: which requests should leave through netdash? An agent makes two kinds of network requests: calls to the model provider's API, and the requests its browser or tools send to websites. Only the second kind needs an exit country or a sticky session, so that is where the netdash proxy goes. For the architecture and the choice of plan, see [proxies for AI agents](https://netdash.io/solutions/ai-agents). netdash is a standard HTTP proxy with username and password authentication. HTTPS sites go through a CONNECT tunnel, so the proxy URL always starts with `http://`. No SDK or plugin is needed: every framework below already has a place for a proxy, or runs a browser or HTTP client that has one. ## Where the proxy goes in an agent _Agent flow: AI agent (your code) → browser or HTTP client configured with a proxy → netdash proxy → approved web resource. netdash provides the network layer only._ | Framework | Component that takes the proxy | Setting | |---|---|---| | Browser Use | The browser session (`Browser`) | `proxy=ProxySettings(server, username, password)` | | OpenAI Agents SDK | The HTTP client inside a function tool, or the browser in your computer-use implementation | `httpx.Client(proxy=...)`, `chromium.launch(proxy=...)` | | LangChain | The HTTP client or browser inside your tools | `httpx.Client(proxy=...)`, `chromium.launch(proxy=...)` | | Playwright MCP, from any MCP client | The browser that the MCP server launches | `browser.launchOptions.proxy` in its config file | In every case the model client keeps its default network path. The examples use the Scale plan on the Americas gateway (`scale-us.netdash.io:2000`) or the Europe gateway (`scale-eu.netdash.io:2000`); the [proxy format reference](https://netdash.io/docs/proxy-format) lists all endpoints and username parameters. The code reads your credentials from `NETDASH_USERNAME` and `NETDASH_PASSWORD`; the [quickstart](https://netdash.io/docs/quickstart) shows where the dashboard displays them. ## Browser Use Browser Use takes the proxy on the browser session. `Browser` is an alias of `BrowserSession`, and its `proxy` parameter takes a `ProxySettings` object with `server`, `username`, `password` and an optional `bypass` list of hosts that bypass the proxy. Browser Use passes the server to Chromium and answers the proxy's authentication request with the username and password itself, so the credentials stay out of the server URL. browser_use_agent.py: ```python import asyncio import os from browser_use import Agent, Browser, ChatBrowserUse from browser_use.browser.profile import ProxySettings async def main() -> None: browser = Browser( proxy=ProxySettings( server="http://scale-us.netdash.io:2000", # One session ID per agent run keeps the run on one exit IP username=f"{os.environ['NETDASH_USERNAME']}-cc-US-session-a1b2c3d4-ttl-30", password=os.environ["NETDASH_PASSWORD"], bypass="localhost,127.0.0.1", ) ) agent = Agent( task="Open https://api.ipify.org?format=json and report the IP address shown", llm=ChatBrowserUse(), # the chat model from the Browser Use quickstart browser=browser, ) await agent.run() asyncio.run(main()) ``` _Syntax-checked._ The model class comes from the Browser Use quickstart; swap in whichever chat model your project uses. Browser Use changes quickly, so check these parameters against the version you install. The proxy belongs to the `Browser`, not to the `Agent`. ## OpenAI Agents SDK Function tools in the OpenAI Agents SDK are plain Python functions, so the proxy goes on the HTTP client inside the tool. The tool below reuses the `proxy_url()` helper from the [Python guide](https://netdash.io/developers/python) and fetches pages from Germany through the Europe gateway. For computer use, the SDK expects you to provide the computer implementation, and its example launches a Playwright browser: add `proxy=` to that launch. Function tool: ```python import os from typing import Optional import httpx from agents import Agent, Runner, function_tool NETDASH_USERNAME = os.environ["NETDASH_USERNAME"] NETDASH_PASSWORD = os.environ["NETDASH_PASSWORD"] def proxy_url(country: str = "US", session_id: Optional[str] = None, ttl_minutes: int = 30, gateway: str = "scale-us.netdash.io:2000") -> str: """netdash proxy URL: rotating without session_id, sticky with one.""" cc = country.upper() cc = "GB" if cc == "UK" else cc username = f"{NETDASH_USERNAME}-cc-{cc}" if session_id is not None: username += f"-session-{session_id}-ttl-{ttl_minutes}" return f"http://{username}:{NETDASH_PASSWORD}@{gateway}" PROXY = proxy_url("DE", gateway="scale-eu.netdash.io:2000") @function_tool def fetch_page(url: str) -> str: """Fetch a public web page as seen from Germany and return the first 5,000 characters.""" with httpx.Client(proxy=PROXY, timeout=30, follow_redirects=True) as client: response = client.get(url) response.raise_for_status() return response.text[:5000] agent = Agent( name="Market researcher", instructions="Use fetch_page for web pages. If a page shows a challenge or an error, stop and report it.", tools=[fetch_page], ) result = Runner.run_sync(agent, "What does https://example.com/ say?") print(result.final_output) ``` Computer use (Playwright launch): ```python import os from playwright.async_api import Browser, Playwright NETDASH_PROXY = { "server": "http://scale-us.netdash.io:2000", "username": f"{os.environ['NETDASH_USERNAME']}-cc-US-session-a1b2c3d4-ttl-30", "password": os.environ["NETDASH_PASSWORD"], } async def launch_browser(playwright: Playwright) -> Browser: """Use this launch in your AsyncComputer implementation, for example in LocalPlaywrightComputer._get_browser_and_page in the SDK's computer-use example.""" return await playwright.chromium.launch(headless=True, proxy=NETDASH_PROXY) ``` _Syntax-checked._ The SDK's hosted tools, such as `WebSearchTool`, run on OpenAI's servers, so a proxy you configure locally doesn't apply to them. ## LangChain LangChain tools are Python functions with the `@tool` decorator, and the docstring becomes the tool's description. Put the proxy on the client inside each tool and pass the tools to `create_agent`. The browser tool launches Playwright with the same `proxy` option as the [Playwright guide](https://netdash.io/developers/playwright); with a sticky username, every call in a run leaves from the same exit IP. HTTP tool: ```python import os import httpx from langchain.agents import create_agent from langchain.tools import tool PROXY = ( f"http://{os.environ['NETDASH_USERNAME']}-cc-US:{os.environ['NETDASH_PASSWORD']}" "@scale-us.netdash.io:2000" ) @tool def fetch_page(url: str) -> str: """Fetch a public web page as seen from the United States and return the first 5,000 characters.""" with httpx.Client(proxy=PROXY, timeout=30, follow_redirects=True) as client: response = client.get(url) response.raise_for_status() return response.text[:5000] # model: a "provider:model" string or a chat model instance, as in the LangChain docs agent = create_agent(model="PROVIDER:MODEL", tools=[fetch_page]) result = agent.invoke({"messages": [{"role": "user", "content": "Summarize https://example.com/"}]}) print(result["messages"][-1].content_blocks) ``` Browser tool: ```python import os from langchain.tools import tool from playwright.sync_api import sync_playwright NETDASH_PROXY = { "server": "http://scale-us.netdash.io:2000", "username": f"{os.environ['NETDASH_USERNAME']}-cc-US-session-a1b2c3d4-ttl-30", "password": os.environ["NETDASH_PASSWORD"], } @tool def read_page(url: str) -> str: """Open a page in a headless browser and return its visible text (first 5,000 characters).""" with sync_playwright() as p: browser = p.chromium.launch(proxy=NETDASH_PROXY) try: page = browser.new_page() page.goto(url, timeout=30_000) return page.inner_text("body")[:5000] finally: browser.close() ``` _Syntax-checked._ The browser tool uses Playwright's sync API, which suits an agent you call with `invoke()`. In an async agent or a notebook, write the tool with Playwright's async API instead. ## Playwright MCP Any MCP client that starts Playwright MCP can give its browser a netdash proxy the same way. Playwright MCP's `--proxy-server` option takes a server address only, so the credentials go in its config file under `browser.launchOptions.proxy`, which the README documents as options passed to Playwright's browser launch. Start the server with `--isolated` so the browser profile stays in memory, and keep the config file outside the repository, readable only by you. MCP client configuration: ```json { "mcpServers": { "playwright": { "command": "npx", "args": ["@playwright/mcp@latest", "--isolated", "--config", "/path/to/playwright-mcp.json"] } } } ``` playwright-mcp.json: ```json { "browser": { "launchOptions": { "proxy": { "server": "http://scale-us.netdash.io:2000", "username": "USERNAME-cc-US-session-a1b2c3d4-ttl-30", "password": "PASSWORD" } } } } ``` _Syntax-checked._ We checked this config file with Playwright MCP 0.0.83 and Chrome 155 against a local test gateway: with `--isolated` and with a persistent profile, the gateway received the username and password on plain-HTTP requests and on the CONNECT for HTTPS pages. The file location and top-level key of the MCP client configuration differ by client. The full walkthrough for Claude Code and Cursor, with `claude mcp add`, a shared `.mcp.json` and a check of the exit IP, is in [Claude Code with Playwright MCP](https://netdash.io/developers/claude-code#claude-code-with-a-playwright-mcp-proxy). ## Keep model API calls out of your AI agent framework proxy setup Don't export `HTTPS_PROXY` for the agent process. A global `HTTPS_PROXY` applies to every request the process makes, including calls to model APIs and package registries. Those requests would go through netdash and count against your balance. Set the proxy on the client that fetches websites instead. A model SDK built on an HTTP library that honors proxy variables picks the variable up without being asked. Check yours: | Library | Reads `HTTPS_PROXY` from the environment? | |---|---| | HTTPX | Yes, by default; `trust_env=False` turns it off. An explicit `proxy=` on a client is what the tools above use. | | HTTPX2 | Yes, by default; `trust_env=False` turns it off. | | OpenAI Python SDK | Its default HTTP client is built on HTTPX2 and keeps `trust_env=True`. For a proxy, the SDK documents a custom HTTP client (`http_client=`). | The same check applies to coding agents: Claude Code, for example, reads `HTTPS_PROXY` for its own API traffic. The [Claude Code guide](https://netdash.io/developers/claude-code#dont-export-httpsproxy-for-claude-code) shows how to keep netdash out of it. ## Sessions, countries and parallel agents - **One session ID per run.** Derive 8 letters and digits from the run ID, for example `hashlib.sha256(run_id.encode()).hexdigest()[:8]` in Python, and use it in `-session-{id}-ttl-30`. The run keeps one exit IP for the TTL while that IP stays available, and runs with different IDs get different sessions. - **One country per run.** The username carries one country code. A run that needs two markets needs two browsers or clients. Use `GB` for the United Kingdom, not `UK`, and the `-eu` gateway for exits in Europe; see [country targeting](https://netdash.io/docs/targeting). - **Pace per site.** netdash sets no per-account concurrency limit, so the limit that matters is the website's. Cap concurrent requests per site, and give each run a budget. - **TTL longer than the task.** The dashboard presets are 15 minutes, 30 minutes, 12 hours and 7 days; the [sessions docs](https://netdash.io/docs/sessions) explain them. ## Responsible use for agents An agent decides on its own which pages to open, so put the limits in its instructions and its code. Tell it to stop and hand the task to a person when a site shows a challenge, a login wall or an error, instead of trying again. Keep it to public pages and APIs the project is permitted to use, and give it no credentials for sites it shouldn't sign in to. > **Use it responsibly** Respect robots.txt and each site's terms, keep request rates reasonable and back off when a site slows you down. Don't collect personal data without a lawful basis or open content behind logins or paywalls without authorization. netdash doesn't solve CAPTCHAs or bot challenges, and your agent shouldn't try to get past them either: if a site rate-limits or challenges your requests, slow down, check its terms, or use its official API. Read [how we expect netdash to be used](https://netdash.io/responsible-use). ## Errors and fixes | Symptom | Cause | Fix | |---|---|---| | Model calls show up on your netdash balance, or the model API becomes unreachable | `HTTPS_PROXY` exported for the agent process | Unset it; set the proxy on the browser or the tool's client | | The agent's browser leaves from your own IP | The proxy went to the wrong object, for example the `Agent` instead of the `Browser`, or a Browser Use version with different parameters | Pass `proxy=ProxySettings(...)` to `Browser`; check the parameters of your installed version | | A 407 or a proxy sign-in prompt in the browser | Credentials written into `server` | Put them in `username` and `password` | | `httpx.ProxyError` from a tool | Wrong password, a malformed username or the wrong host and port | Check `-cc-` and the session parameters against the proxy format reference | | Playwright reports that the Sync API is used inside the asyncio loop | A sync browser tool runs inside an async agent or a notebook | Use Playwright's async API in that tool | | A tool keeps returning 403 or challenge pages | The site is limiting the agent | Stop, check the site's terms, or use its official API | > netdash forwards your traffic unchanged. It doesn't solve CAPTCHAs, render JavaScript or retry requests. Targeting is country-level. Keep `NETDASH_USERNAME` and `NETDASH_PASSWORD` in a `.env` file or your secret manager, never in source control or in a chat with an assistant. ## Next steps - [Claude Code and Playwright MCP](https://netdash.io/developers/claude-code): Credentials in .env, per-client configuration and an authenticated Playwright MCP browser. - [Sticky and rotating sessions](https://netdash.io/docs/sessions): Session IDs, TTLs in minutes and the dashboard presets. - [Proxies for AI agents](https://netdash.io/solutions/ai-agents): Where netdash fits in an agent architecture, and how to choose exits and sessions. Also useful: [cloud browser proxy setup](https://netdash.io/developers/cloud-browsers), the [Playwright](https://netdash.io/developers/playwright) and [Python](https://netdash.io/developers/python) guides, [browser automation with netdash](https://netdash.io/solutions/browser-automation) and [country targeting](https://netdash.io/docs/targeting). Stuck? See [troubleshooting](https://netdash.io/docs/troubleshooting) or email support with your username (without the password) and the error message. [Email support](mailto:support@netdash.io?subject=Proxy%20troubleshooting&body=Plan%3A%0AGateway%20host%3Aport%3A%0AUsername%20%28no%20password%29%3A%0ATime%20%28UTC%29%3A%0AWebsite%20domain%3A%0AError%20message%3A%0AClient%20and%20version%3A%0A) ## Sources - [Browser Use docs: All browser parameters (proxy, Browser as an alias of BrowserSession)](https://docs.browser-use.com/open-source/customize/browser/all-parameters) (accessed 2026-10-09) - [Browser Use docs: Browser basics (Agent with task, browser and llm)](https://docs.browser-use.com/open-source/customize/browser/basics) (accessed 2026-10-09) - [Browser Use source: ProxySettings in browser_use/browser/profile.py](https://github.com/browser-use/browser-use/blob/main/browser_use/browser/profile.py) (accessed 2026-10-09) - [Browser Use source: proxy authentication in browser_use/browser/session.py](https://github.com/browser-use/browser-use/blob/main/browser_use/browser/session.py) (accessed 2026-10-09) - [OpenAI Agents SDK docs: Tools (function tools, hosted tools, computer use)](https://openai.github.io/openai-agents-python/tools/) (accessed 2026-10-09) - [OpenAI Agents SDK example: examples/tools/computer_use.py](https://github.com/openai/openai-agents-python/blob/main/examples/tools/computer_use.py) (accessed 2026-10-09) - [OpenAI Python SDK README: Configuring the HTTP client](https://github.com/openai/openai-python#configuring-the-http-client) (accessed 2026-10-09) - [OpenAI Python SDK: HTTPX2 migration guide (trust_env default)](https://github.com/openai/openai-python/blob/main/httpx2.md) (accessed 2026-10-09) - [HTTPX2 docs: Environment variables (HTTP_PROXY, HTTPS_PROXY, trust_env)](https://pydantic.dev/docs/httpx2/api/environment_variables/) (accessed 2026-10-09) - [HTTPX docs: Environment variables](https://www.python-httpx.org/environment_variables/) (accessed 2026-10-09) - [LangChain docs: Tools (@tool)](https://docs.langchain.com/oss/python/langchain/tools) (accessed 2026-10-09) - [LangChain docs: Agents (create_agent)](https://docs.langchain.com/oss/python/langchain/agents) (accessed 2026-10-09) - [LangChain docs: Quickstart (invoke and reading the reply)](https://docs.langchain.com/oss/python/langchain/quickstart) (accessed 2026-10-09) - [Playwright MCP README (--config, --isolated, configuration file schema)](https://github.com/microsoft/playwright-mcp) (accessed 2026-10-09) - [Playwright API: browserType.launch(), proxy option](https://playwright.dev/docs/api/class-browsertype#browser-type-launch-option-proxy) (accessed 2026-10-09) --- # Proxies for cloud browsers: VMs, containers and hosted services > Where the browser runs and how its traffic leaves are separate decisions. Configure the netdash proxy where the browser is launched: in your own code on a VM or container, or in a hosted service's session settings. Source: https://netdash.io/developers/cloud-browsers · Updated 2026-10-09 A cloud browser proxy setup has two separate parts: the machine or service the browser runs on, and the way its traffic leaves for the web. netdash only covers the second part. It isn't a browser host: you run the browser yourself, on a VM or in a container, or you rent one from a hosted browser service and give that service your netdash proxy. For the agent side of this setup, see [proxies for AI agents](https://netdash.io/solutions/ai-agents). netdash is an HTTP proxy with username and password authentication. HTTPS sites go through a CONNECT tunnel, so the proxy server always starts with `http://`. The username carries the exit country and, for a sticky session, a session ID and a TTL in minutes. ## Three layers, configured separately _Three layers: (1) where the browser runs: laptop, cloud VM, container or a third-party managed browser; (2) how its traffic leaves: the netdash proxy; (3) where the traffic goes: sites you are permitted to access._ | Layer | What you decide | Where it's set | |---|---|---| | 1. Where the browser runs | A laptop, a cloud VM, a container, or a hosted browser service | Your infrastructure, or your account with the service | | 2. How its traffic leaves | The netdash plan, gateway, exit country and session | The browser's launch options, or the hosted service's session settings | | 3. Where the traffic goes | Sites and APIs you're permitted to access | Your task and the site's terms, not a proxy setting | Moving a browser to the cloud changes layer 1 only. Without a proxy, a browser on a cloud VM reaches websites from the cloud provider's address in that region. With the netdash proxy, it reaches them from an exit IP in the country you choose, wherever the browser runs. ## Your own browser on a VM or container Launch the browser exactly as in the [Playwright](https://netdash.io/developers/playwright) and [Puppeteer](https://netdash.io/developers/puppeteer) guides: the proxy settings don't change on a server. Three things do need attention: - **Credentials at run time.** Pass `NETDASH_USERNAME` and `NETDASH_PASSWORD` as environment variables when the container starts. Don't bake them into an image or commit them in a Compose file. - **Outbound access.** The VM or container needs outbound TCP to the gateway host on your plan's port: 1000, 2000, 3000 or 4000, as listed in the [proxy format reference](https://netdash.io/docs/proxy-format). The examples use `scale-us.netdash.io:2000`. - **A pinned image.** Playwright's Docker docs recommend pinning the image to a specific version; it should match the Playwright version in your project. Run a Playwright script in a container: ```bash # NETDASH_USERNAME and NETDASH_PASSWORD come from your shell or CI secrets. # "-e NAME" without a value copies the variable from the current environment. docker run --rm --init --ipc=host \ --user pwuser --security-opt seccomp=seccomp_profile.json \ -e NETDASH_USERNAME -e NETDASH_PASSWORD \ -v "$PWD":/work -w /work \ mcr.microsoft.com/playwright:v1.64.0-noble \ node contexts.mjs ``` _Syntax-checked._ Playwright's Docker docs recommend `--init`, and `--ipc=host` when you use Chromium. For sites you don't control, the same docs recommend running the browser as a separate user (`pwuser`) together with a seccomp profile, which they link to. `contexts.mjs` stands for your own script, for example the per-context example from the [Playwright guide](https://netdash.io/developers/playwright), with Playwright installed in the project at the image's version. Keep `NETDASH_USERNAME` and `NETDASH_PASSWORD` in a `.env` file or your secret manager, never in source control or in a chat with an assistant. ## Remote browsers you connect to When your code connects to a browser that runs somewhere else, over the Chrome DevTools Protocol or another WebSocket endpoint, the proxy belongs to that browser's launch, not to the code that connects. Set it where the browser is started: in the launch command on your own browser server, or in the hosted service's session settings below. Some clients let you create a new browser context with its own `proxy` over a remote connection. Whether that works depends on the connection type and the service, so confirm the exit IP with `https://api.ipify.org?format=json` before you rely on it. ## Cloud browser proxy settings in hosted services Several hosted browser services accept your own proxy when you create a session. Each needs the same three values from netdash: the gateway (`scale-us.netdash.io`, port `2000`), a username with the country and, for a sticky session, the session parameters, and your password. The configurations below follow each provider's documentation, linked under Sources. They are not tested by netdash, and netdash has no partnership with these providers. Some services accept an external proxy only on certain plans, so check your plan with the provider. If a service also sells its own proxies, use one proxy layer for a given site, not both. ### Browserbase Pass an `external` proxy when you create the session. Browserbase validates the proxy connection at session creation and throws an error if it can't connect, so a wrong password or a blocked port shows up right away. Its docs also say that not all proxy providers are supported, so start with a short test session. An optional `domainPattern` limits an entry to matching domains; entries apply in order and the first match wins. Browserbase lists custom proxies for its Developer plan and above. Browserbase session (Node.js): ```js import { Browserbase } from "@browserbasehq/sdk"; const bb = new Browserbase({ apiKey: process.env.BROWSERBASE_API_KEY }); const session = await bb.sessions.create({ proxies: [ { type: "external", server: "http://scale-us.netdash.io:2000", username: `${process.env.NETDASH_USERNAME}-cc-US-session-a1b2c3d4-ttl-30`, password: process.env.NETDASH_PASSWORD, }, ], }); console.log("Session:", session.id); ``` _Syntax-checked._ Connect your Playwright or Puppeteer client to the session as the Browserbase docs describe, then open `https://api.ipify.org?format=json` to see the exit IP. ### Amazon Bedrock AgentCore Browser AgentCore takes the proxy in `proxyConfiguration` when you call `StartBrowserSession`, and you can't change it during the session: start a new session for a different country or session ID. The `server` field is a hostname without a scheme, the port has its own field, and the credentials come from an AWS Secrets Manager secret with `username` and `password` keys. The identity that starts the session needs `secretsmanager:GetSecretValue` on that secret. AgentCore requires an HTTP or HTTPS proxy that supports CONNECT for HTTPS, which is how netdash works. Usernames may contain letters, digits and `@ . _ + = -`, so the `-cc-` and `-session-` parameters fit; check that your password uses only the characters the AWS docs allow (no colons, spaces or quotes). Domains in `bypass.domainPatterns` bypass the proxy and connect directly; the AWS docs suggest that for AWS endpoints. Secret (AWS CLI): ```bash aws secretsmanager create-secret --name "netdash-us-session-a1b2c3d4" \ --secret-string '{"username":"USERNAME-cc-US-session-a1b2c3d4-ttl-30","password":"PASSWORD"}' ``` Session (boto3): ```python import boto3 client = boto3.client("bedrock-agentcore", region_name="us-east-1") response = client.start_browser_session( browserIdentifier="aws.browser.v1", name="netdash-us-session", proxyConfiguration={ "proxies": [{ "externalProxy": { "server": "scale-us.netdash.io", # hostname only, no scheme "port": 2000, # The ARN that create-secret printed "credentials": {"basicAuth": {"secretArn": "arn:aws:secretsmanager:REGION:ACCOUNT_ID:secret:netdash-us-session-a1b2c3d4"}}, } }], "bypass": {"domainPatterns": [".amazonaws.com"]}, }, ) print("Session:", response["sessionId"]) ``` _Syntax-checked._ Each secret holds one username, so create one secret per country and session combination. AgentCore doesn't validate the proxy when the session starts: an unreachable gateway or a wrong password shows up as browser error pages, which you can see in Live View. It doesn't retry failed requests either. ### Browserless Browserless takes an external proxy as the `externalProxyServer` query parameter on the connection URL, with the credentials inside the proxy URL. URL-encode the proxy URL, as its docs say, especially when the password contains special characters. Browserless documents external proxies as HTTP(S) only and rejects them on its free plan with a 401. Browserless with Puppeteer (Node.js): ```js import puppeteer from "puppeteer-core"; const { BROWSERLESS_TOKEN, NETDASH_USERNAME, NETDASH_PASSWORD } = process.env; const username = `${NETDASH_USERNAME}-cc-US-session-a1b2c3d4-ttl-30`; const proxyUrl = `http://${username}:${encodeURIComponent(NETDASH_PASSWORD)}@scale-us.netdash.io:2000`; const browser = await puppeteer.connect({ browserWSEndpoint: `wss://production-sfo.browserless.io?token=${BROWSERLESS_TOKEN}&externalProxyServer=${encodeURIComponent(proxyUrl)}`, }); try { const page = await browser.newPage(); await page.goto("https://api.ipify.org?format=json", { timeout: 30_000 }); console.log(await page.evaluate(() => document.body.innerText)); } finally { await browser.close(); } ``` _Syntax-checked._ Use the regional endpoint from your Browserless account in place of `production-sfo`. ## Sessions and countries in hosted browsers The username carries the exit country and the session, so each hosted session gets the netdash username you give it at creation: - **Browserbase:** one `external` entry per country, with a `domainPattern` when different sites need different exit countries. - **AgentCore:** one secret per username, and a new browser session to change it. - **Browserless:** the proxy is part of the connection URL, so each connection can use its own username. Reusing a session ID keeps the same exit IP for the TTL while that IP stays available. Pick a TTL longer than the hosted session runs; the dashboard presets are 15 minutes, 30 minutes, 12 hours and 7 days. Use `GB` for the United Kingdom, not `UK`, and the `-eu` gateway for exits in Europe. The [sticky session syntax](https://netdash.io/docs/sessions) has the details. ## Errors and fixes | Symptom | Cause | Fix | |---|---|---| | Every page times out from your own VM or container | A firewall or security group blocks outbound traffic to the gateway port | Allow outbound TCP to the gateway host on your plan's port | | Browserbase session creation fails with a proxy error | Browserbase couldn't connect through the proxy at creation: wrong host, port or password | Test the same values with the cURL example from the [quickstart](https://netdash.io/docs/quickstart), then create the session again | | AgentCore session starts, but pages show proxy errors | AgentCore doesn't validate the proxy at creation; the server, port or secret is wrong | Check `server` (hostname only), `port` and the secret's `username` and `password` keys; start a new session | | AgentCore pages fail even with the right password | The secret contains characters AgentCore doesn't accept | Check the allowed characters in the AWS docs | | Browserless answers 401 when you pass `externalProxyServer` | Third-party proxies need a paid Browserless plan | Check your plan with Browserless | | Browserless can't use the proxy | The proxy URL isn't URL-encoded in the query string | Wrap it in `encodeURIComponent` | | The exit IP is in the wrong country | The service's own proxy and your external proxy are both configured for the site, or the username has another `-cc-` value | Use one proxy layer per site; check the country code in the username | > netdash forwards your traffic unchanged. It doesn't solve CAPTCHAs, render JavaScript or retry requests. Targeting is country-level. > **Use it responsibly** Respect robots.txt and each site's terms, keep request rates reasonable and back off when a site slows you down. Don't collect personal data without a lawful basis or open content behind logins or paywalls without authorization. netdash doesn't solve CAPTCHAs or bot challenges, and your browsers shouldn't try to get past them either: if a site rate-limits or challenges your requests, slow down, check its terms, or use its official API. Read [how we expect netdash to be used](https://netdash.io/responsible-use). ## Next steps - [Playwright proxy setup](https://netdash.io/developers/playwright): Launch and context proxies in Playwright for Node.js and Python, including Playwright Test. - [Troubleshooting](https://netdash.io/docs/troubleshooting): 407 errors, connection failures, timeouts and the wrong exit country, with the cause of each. - [Browser automation](https://netdash.io/solutions/browser-automation): Run browser workflows from a chosen country with per-context sessions. Also useful: the [Puppeteer guide](https://netdash.io/developers/puppeteer), the [agent frameworks guide](https://netdash.io/developers/agent-frameworks) and [rotating and sticky sessions](https://netdash.io/docs/sessions). Stuck? See [troubleshooting](https://netdash.io/docs/troubleshooting) or email support with your username (without the password) and the error message. [Email support](mailto:support@netdash.io?subject=Proxy%20troubleshooting&body=Plan%3A%0AGateway%20host%3Aport%3A%0AUsername%20%28no%20password%29%3A%0ATime%20%28UTC%29%3A%0AWebsite%20domain%3A%0AError%20message%3A%0AClient%20and%20version%3A%0A) ## Sources - [Playwright docs: Docker (image tags, version pinning, recommended flags)](https://playwright.dev/docs/docker) (accessed 2026-10-09) - [Docker docs: docker container run, setting environment variables (-e)](https://docs.docker.com/reference/cli/docker/container/run/#env) (accessed 2026-10-09) - [Browserbase docs: Proxies (external proxies, domainPattern, validation at session creation)](https://docs.browserbase.com/features/proxies) (accessed 2026-10-09) - [Amazon Bedrock AgentCore Developer Guide: Using browser proxies](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/browser-proxies.html) (accessed 2026-10-09) - [Browserless docs: Proxies (externalProxyServer)](https://docs.browserless.io/baas/features/proxies) (accessed 2026-10-09)