ISP proxies
An ISP purchase is a batch: a set of dedicated IPs sharing one credential pair, valid for a fixed term. Bandwidth is unlimited, so the only thing that runs out is time — the remaining validity is on the plan page.
You can authenticate three ways, switchable at any time:
- Username & password — generated for you, shared across the batch.
- IP whitelist — no credentials; only the addresses you list may connect.
- SOCKS5 — SOCKS5 credentials, also generated automatically.
Rotating residential
Residential is bought as a gigabyte allowance, not as a fixed set of IPs. You generate proxy strings on demand and every request draws from the same allowance. Data does not expire, and topping up merges into the same plan without changing your credentials.
curl -x "user_9fx-us-california:pass@residential.natproxies.com:9000" \
https://api.ipify.orgMobile 4G/5G
Mobile works the same way as residential but is backed by real carrier addresses shared by thousands of handsets, which makes them the hardest addresses to distinguish from an ordinary phone.
Mobile strings are colon-delimited rather than URL-form, and geo-targeting is country and city only — there is no state filter.
mobile.your-domain.com:5555:pkg-country-us-session-1:k5fcv99xfa1e61je
Geo-targeting
Choose a country when you generate a list. Residential additionally accepts a state or region and a city; mobile accepts a city. Narrower targeting draws from a smaller pool, so if a city returns very few working exits, widen to the state or country.
Sessions
By default every request gets a new exit IP. A sticky session holds the same exit for a set duration, which is what you want for anything that carries a login or a cart across several requests.
- Rotating — new IP per request. Best for scraping breadth.
- Sticky — same IP for up to 30 minutes. Best for sessions and checkouts.
Python
Every example uses user:pass authentication. If your batch is on IP whitelist instead, drop the credentials and connect to host:port directly.
# requests
import requests
proxy = "http://USER:PASS@HOST:PORT"
r = requests.get(
"https://api.ipify.org?format=json",
proxies={"http": proxy, "https": proxy},
timeout=30,
)
print(r.json()) # the exit IP you are usingFor many requests, reuse the session rather than reconnecting each time:
session = requests.Session()
session.proxies = {"http": proxy, "https": proxy}
for url in urls:
r = session.get(url, timeout=30)httpx, if you prefer async:
import httpx
async with httpx.AsyncClient(proxy="http://USER:PASS@HOST:PORT") as client:
r = await client.get("https://example.com")pip install requests[socks], then usesocks5://USER:PASS@HOST:PORT. Use socks5h:// to resolve DNS through the proxy rather than locally — which is usually what you want, since local DNS leaks your real location.Node.js
// undici — built into modern Node
import { ProxyAgent, request } from 'undici'
const agent = new ProxyAgent('http://USER:PASS@HOST:PORT')
const { body } = await request('https://api.ipify.org?format=json', {
dispatcher: agent,
})
console.log(await body.json())With axios, via an agent:
import axios from 'axios'
import { HttpsProxyAgent } from 'https-proxy-agent'
const agent = new HttpsProxyAgent('http://USER:PASS@HOST:PORT')
const { data } = await axios.get('https://example.com', {
httpAgent: agent,
httpsAgent: agent,
})httpsAgent as well as httpAgent. Setting only the first is the most common reason an axios request quietly bypasses the proxy entirely — nothing errors, the traffic simply goes out from your own IP.cURL
# HTTP proxy
curl -x "http://USER:PASS@HOST:PORT" https://api.ipify.org
# SOCKS5, resolving DNS through the proxy
curl -x "socks5h://USER:PASS@HOST:PORT" https://api.ipify.org
# IP-whitelisted batch — no credentials needed
curl -x "http://HOST:PORT" https://api.ipify.org
# Check what a site sees, with timing
curl -x "http://USER:PASS@HOST:PORT" \
-w "\n%{http_code} in %{time_total}s\n" \
-o /dev/null -s https://example.comThe first command is the fastest way to confirm a proxy works at all: it returns the exit IP. If that IP is the proxy's rather than yours, everything downstream is configuration.
Playwright
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(
proxy={
"server": "http://HOST:PORT",
"username": "USER",
"password": "PASS",
}
)
page = browser.new_page()
page.goto("https://api.ipify.org")
print(page.content())
browser.close()Node:
const browser = await chromium.launch({
proxy: { server: 'http://HOST:PORT', username: 'USER', password: 'PASS' },
})username and password, not in the server URL. Chromium ignores credentials embedded in the proxy URL, sohttp://user:pass@host:port silently authenticates as nobody.For a different exit per browser context, launch one browser and pass a separate proxy per context:
const context = await browser.newContext({
proxy: { server: 'http://HOST:PORT', username: 'USER', password: 'PASS' },
})Selenium
Selenium is the awkward one: Chrome's --proxy-server flag accepts no credentials. With an IP-whitelisted batch it is trivial:
from selenium import webdriver
opts = webdriver.ChromeOptions()
opts.add_argument("--proxy-server=http://HOST:PORT")
driver = webdriver.Chrome(options=opts)
driver.get("https://api.ipify.org")user:pass authentication, whitelist your server's IP instead and use the snippet above. The usual workaround — a generated extension that fills the auth prompt — adds a moving part that breaks on Chrome updates, and it is avoidable.Firefox, with SOCKS5:
from selenium import webdriver
profile = webdriver.FirefoxProfile()
profile.set_preference("network.proxy.type", 1)
profile.set_preference("network.proxy.socks", "HOST")
profile.set_preference("network.proxy.socks_port", PORT)
profile.set_preference("network.proxy.socks_remote_dns", True)
driver = webdriver.Firefox(firefox_profile=profile)Scrapy
# settings.py
DOWNLOADER_MIDDLEWARES = {
"scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware": 110,
}
# middlewares.py — set the proxy per request
class ProxyMiddleware:
def process_request(self, request, spider):
request.meta["proxy"] = "http://USER:PASS@HOST:PORT"CONCURRENT_REQUESTS and DOWNLOAD_DELAY deliberately. A rotating pool spreads addresses, but a hundred simultaneous requests still arrive at one site as a hundred simultaneous requests.Troubleshooting
- 407 Proxy Authentication Required — the credentials changed, usually because the batch's authentication mode was switched. Regenerate the list.
- Connection refused on an ISP batch — if it is on IP whitelist mode, check your current public IP is still on the list.
- Traffic stops on a bandwidth plan — check the usage bar on the plan page. An exhausted allowance stops serving until you top up.
- Very few IPs for a city — widen the targeting to the state or country.