n8n residential proxy pipeline
Pin the country
Do: encode -country-XX on the username, pick Premium HTTP 2099 for a national catalog, add Elite city only when local stock is the object, then verify with an IP lookup through the same proxy URL. Why: storefronts localize catalog and currency by exit country. Wrong: testing in the editor on a US username and scheduling the same string as a DE job. Store: requested_country, resolved_country, and the lookup timestamp.
Set HTTP Request Proxy
Do: Options → Add option → Proxy, full URL with scheme, password-only URL-encoding, timeout 20–30s, Include Response Headers and Status. Why: the official field is an HTTP proxy; a missing scheme can send the request direct; that option puts statusCode and body on the item. Wrong: socks5:// in that field, fetching from a Code node, or treating a red 407 as a classified item. Store: the proxy host and port, not the password.
Verify the exit
Do: GET an IP lookup through the same Proxy string before the target URL. Pass requested_country (and requested_city on Elite) into the next node; stop the catalog GET on mismatch. Why: asking for a country is not landing there. Wrong: hard-coding geoOk = true, or trusting the username as resolved_country. Store: exit_ip, requested_country, resolved_country, requested_city, resolved_city.
Classify the body
Do: in a Code node, label populated only when you extracted at least one {title, price}; else empty, consent, challenge, unclassified, or wrong_geo. Why: HTTP 200 is not success — a long error page is not a catalog. Wrong: writing prices=[] for a consent wall, or treating a red 407 node as a target block. Store: status, and persist items only when status is populated.
Rotate or hold sticky
Do: omit -session- for independent URLs; set the same -session-<id>-lifetime-N on every node that must share an exit. Why: pagination and cookies bind to the IP that minted them. Wrong: one rotating string across a multi-page walk, or one sticky IP for a 5,000-URL morning run. Store: session id next to collected_at when sticky was on.

How to use residential proxies with n8n
- 1
Pin the country and build one proxy URL per identity
Name the object before you open n8n. Country targeting (-country-de) owns catalog, currency, and language for a national storefront. City targeting (-city-, Elite only) owns local stock and delivery when that is actually the metric. ISP targeting is a separate Elite flag and is usually unnecessary for HTTP Request collection. Then build a full HTTP proxy URL. The official HTTP Request option is an HTTP proxy; include the scheme. URL-encode only the password component (encodeURIComponent on AETHYN_PASS) if it contains @, :, /, or other reserved characters — do not encode the username. Store credentials in n8n environment variables so you are not pasting secrets into every Proxy field. Stay on Premium (HTTP 2099) when the job is national catalog. A city or ISP on Premium is an unsupported combo and is refused; a valid Elite city with no matching exit falls back to the closest match, then the country. Asking for a country is not landing there — the next step verifies the exit before any catalog GET.
textPremium country (HTTP Request Proxy field) http://aethyn-XXXXX-country-de:PASSWORD@proxy.aethyn.io:2099 Sticky for one pagination walk (~30 min) http://aethyn-XXXXX-country-de-session-wf1-lifetime-30:PASSWORD@proxy.aethyn.io:2099 Elite city (only when local stock is the object) http://aethyn-XXXXX-country-us-city-chicago:PASSWORD@proxy.aethyn.io:5499 n8n expression — encode the password only, not the username http://{{ $env.AETHYN_USER }}:{{ encodeURIComponent($env.AETHYN_PASS) }}@proxy.aethyn.io:2099cURLcurl -x "http://aethyn-XXXXX-country-de:PASSWORD@proxy.aethyn.io:2099" \ "https://ipinfo.io/json"Field note: cURL here is only to prove the exit. Record resolved_country from the lookup, not from the username you sent. Do not put socks5:// in the HTTP Request Proxy field.
- 2
Minimal workflow: verify the exit, then fetch, then classify
This is the working collector. Name the nodes exactly as written so the Code samples can read them. Set holds requested_country, requested_city (empty on Premium), and target_url. Verify exit is HTTP Request GET to an IP lookup through the same Proxy string, with Include Response Headers and Status on so the item is { body, headers, statusCode }. Check geo reads $('Set') plus $input (the lookup). An IF node on geo_ok stops the catalog GET on mismatch. Fetch catalog is a second HTTP Request to $('Set').target_url with the same Proxy string. Classify reads $('Check geo') and $input (the catalog response). populated is at least one extracted {title, price} — not body length, not HTTP 200. A long error page, a landing page, or a page with no products is empty or unclassified. Proxy CONNECT 407 can fail the node even when Never Error is on; a red node is proxy auth, not a catalog row. Target 403/429 may arrive as items if Never Error is on. Match Accept-Language to the exit. These node names and field names follow n8n HTTP Request docs; they were not executed end-to-end on n8n Cloud for this page.
textSet (requested_country, requested_city, target_url) → HTTP Request "Verify exit" GET ip lookup · same Proxy URL · Include Response Headers and Status → Code "Check geo" reads $('Set') + $input (lookup body.country / body.city) → IF geo_ok is true → HTTP Request "Fetch catalog" GET $('Set').target_url · same Proxy URL · Include Response Headers and Status → Code "Classify" reads $('Check geo') + $input (catalog body + statusCode) IF geo_ok is false → Code "Stop: wrong_geo" no catalog GET · body_class = wrong_geoJavaScript (Check geo)const set = $("Set").first().json; const lookup = $input.first().json; const payload = lookup.body && typeof lookup.body === "object" ? lookup.body : lookup; const requested_country = String(set.requested_country || "").toLowerCase(); const requested_city = String(set.requested_city || "").trim().toLowerCase(); const resolved_country = String(payload.country || payload.countryCode || "").toLowerCase(); const resolved_city = String(payload.city || "").toLowerCase(); const country_ok = Boolean(requested_country) && resolved_country === requested_country; const city_ok = !requested_city || resolved_city.includes(requested_city); const geo_ok = country_ok && city_ok; return [{ json: { target_url: set.target_url, requested_country, requested_city, resolved_country, resolved_city, exit_ip: payload.ip || payload.query || "", lookup_statusCode: lookup.statusCode, geo_ok, }, }];JavaScript (Classify)const CHALLENGE = ["unusual traffic", "sorry/index", "verify you are human", "access denied"]; const CONSENT = ["before you continue", "consent.google.com"]; /** Replace this contract with your catalog. populated requires ≥1 title AND price. */ function extractProducts(body) { const products = []; if (body && typeof body === "object" && !Array.isArray(body)) { const rows = Array.isArray(body.products) ? body.products : []; for (const row of rows) { const title = String(row.title || row.name || "").trim(); const price = String(row.price || row.priceText || "").trim(); if (title && price) products.push({ title, price }); } return products; } const html = String(body || ""); const re = /data-title="([^"]+)"[^>]*data-price="([^"]+)"/gi; let m; while ((m = re.exec(html))) { const title = m[1].trim(); const price = m[2].trim(); if (title && price) products.push({ title, price }); } return products; } function classify({ statusCode, bodyText, geo_ok, products }) { if (!geo_ok) return "wrong_geo"; if (statusCode == null) return "http_error"; if (statusCode === 407) return "http_error"; if (statusCode >= 400) return "http_error"; const low = bodyText.toLowerCase(); if (CHALLENGE.some((m) => low.includes(m))) return "challenge"; if (CONSENT.some((m) => low.includes(m))) return "consent"; if (products.length > 0) return "populated"; if (bodyText.trim()) return "unclassified"; return "empty"; } const verify = $("Check geo").first().json; const fetch = $input.first().json; const statusCode = fetch.statusCode; const rawBody = fetch.body !== undefined ? fetch.body : ""; const bodyText = typeof rawBody === "string" ? rawBody : JSON.stringify(rawBody ?? ""); const products = extractProducts(rawBody); const body_class = classify({ statusCode, bodyText, geo_ok: verify.geo_ok === true, products, }); return [{ json: { url: verify.target_url, requested_country: verify.requested_country, requested_city: verify.requested_city, resolved_country: verify.resolved_country, resolved_city: verify.resolved_city, exit_ip: verify.exit_ip, http_status: statusCode ?? null, body_class, product_count: products.length, collected_at: new Date().toISOString(), }, }];JavaScript (Stop: wrong_geo)const verify = $input.first().json; return [{ json: { url: verify.target_url, requested_country: verify.requested_country, requested_city: verify.requested_city, resolved_country: verify.resolved_country, resolved_city: verify.resolved_city, exit_ip: verify.exit_ip, http_status: null, body_class: "wrong_geo", product_count: 0, collected_at: new Date().toISOString(), }, }];JSON (record shape){ "url": "https://www.example.com/catalog", "requested_country": "de", "requested_city": "", "resolved_country": "de", "resolved_city": "frankfurt", "exit_ip": "203.0.113.10", "http_status": 200, "body_class": "populated", "product_count": 12, "collected_at": "2026-10-02T12:00:00Z" }Field note: Create a trial username, pin a country, and run verify before the catalog GET (/signup). Country equality does not prove a city request — store requested_city next to resolved_city on Elite. Do not copy a placeholder geo_ok = true. This example is documented against n8n HTTP Request docs, not a Cloud capture.
- 3
Rotate per independent URL; sticky only for pagination and cookies
Rotation and pagination are different jobs. Rotation is an Aethyn identity choice: omit -session- so each independent URL (a product page, a sitemap, a public API) gets a new household CONNECT. Pagination is an n8n HTTP Request option that fires more than one request from the same node. Those page requests reuse the Proxy string you configured — they do not invent a session. Without -session- on that string, page two is a different exit than page one. Put the same -session-<id>-lifetime-N (1–1440 minutes, default 30) on every request that must share an IP, then drop the session id when the walk ends. Confirm on your instance that page 1 and page 2 resolve the same exit_ip before you trust the table. Do not pin one sticky IP for a 5,000-URL morning run. Sticky holds a rotating-pool residential IP. It is not a static, dedicated, or ISP product.
text# Independent URLs — rotate (no session id) Proxy: http://aethyn-XXXXX-country-de:PASSWORD@proxy.aethyn.io:2099 # One pagination / cookie walk — same session id on every node in the walk Proxy: http://aethyn-XXXXX-country-de-session-wf1-lifetime-30:PASSWORD@proxy.aethyn.io:2099 # Next job — new session id or omit it Proxy: http://aethyn-XXXXX-country-de-session-wf2-lifetime-30:PASSWORD@proxy.aethyn.io:2099Field note: A Playwright or Puppeteer community node is a different client. Put credentials in username and password fields, not user:pass in the server URL (407). The setup guide is the Playwright integration, not this page.
- 4
When HTTP Request is not enough, change the client — not the country
If requests through HTTP Request come back challenge or empty, and the document you need is a JavaScript grid, the next client is a real browser through the same username: a community Playwright/Puppeteer node, or a Playwright worker that n8n calls. Do not buy a different country string to paper over a JS challenge. Do not fetch from Code. The three-client rule still holds: least expensive client that reliably produces the required document. HTTP Request is the requests-shaped client on this canvas. A browser is Playwright. Copy-paste for launch versus context lives in the Playwright guide. This page stays on the HTTP Request Proxy field.
textIf you observe… Try next Do not HTTP Request 407 Rebuild user/pass; encode pass Rotate IPs / retry as catalog HTTP Request populated, wrong currency New country on the username Add Playwright "to be safe" HTTP Request empty/challenge; JS grid Playwright, same username Buy more residential GB All clients challenge Classify the interstitial Unblock / CAPTCHA recipes wrong_geo New session id; verify again Label a fallback as the countryField note: Community Playwright nodes differ. The invariant is the same as the Playwright integration: credentials in username and password fields, one context per identity, sticky only while the cookie lives.
What are you actually routing through n8n?
n8n is a workflow canvas. The unit of work is a node, not a Python process. The HTTP Request node is how most scraping and geo-check jobs leave the canvas: a method, a URL, optional headers, optional pagination. Without a proxy, every one of those requests exits from n8n Cloud's shared egress or from the single IP of your self-hosted box. Targets see that address, not a household in the market you are reporting. A residential proxy in this stack is not a VPN you toggle for the whole instance. It is a per-node HTTP proxy URL: scheme, username, password, host, port. That URL is an identity. Country, sticky session, and pool all live in it. Two nodes that share one string share one identity. Two nodes that build different strings are two identities, even if they sit on the same canvas. The job of this guide is to make that identity explicit, verify it, and classify the document before you write a row. HTTP 200 is not successful collection.
What breaks proxied n8n workflows
- The HTTP Request Proxy field is a full HTTP URL; a missing scheme or a SOCKS5 URL is not the documented path
- n8n Cloud cannot set process HTTP_PROXY; self-hosted HTTP_PROXY applies to the whole process unless NO_PROXY is set
- Code-node network access depends on the n8n deployment — use HTTP Request for the GET
- One Proxy string reused across nodes glues the editor's country onto the scheduled job
- Pagination without a session id concatenates two households into one table
- Soft 200s (empty shell, consent, challenge, unclassified HTML, wrong country) look like success if you only read the status code or body length

How Aethyn Premium fits n8n HTTP Request nodes
Use Premium when the job is a national catalog, currency, or public HTTP collection by country. Use Elite when the target scores city, ISP, or a harder anti-bot — city targeting lives on Elite. Two different failures: a city or ISP on Premium (port 2099) is an unsupported combo and is refused; a valid Elite city with no matching exit falls back to the closest match, then the country. Store requested_city next to resolved_city. Sticky sessions help only while pagination or a cookie must survive; rotate again when that flow ends. The HTTP Request Proxy field is an HTTP proxy, so use HTTP CONNECT on 2099 (Premium) or 5499 (Elite). SOCKS5 on 1099 / 3499 is the same pool for clients that need a TCP tunnel; this node is not that client. PAYG gigabytes never expire. A proxy does not skip a site's terms, robots.txt, or rate limits.
- Premium HTTP 2099 for country catalog and FX — encode -country-XX; do not put a city on this username
- Elite HTTP 5499 with -country-XX-city- when local stock or a harder target is the object
- Per-request rotation by omitting a session id so each independent URL gets a fresh household exit
- Sticky 1–1440 minutes (default 30) as -session-…-lifetime-N on every node that shares one identity, then drop the session id
- PAYG GB that never expire — weekend n8n jobs do not need a monthly burn
n8n residential proxy FAQ
Where do I set a proxy in n8n?
Can I use SOCKS5 with the HTTP Request node?
Why did my Code node fail to fetch?
n8n Cloud or self-hosted?
What is N8N_PROXY_HOPS?
Premium or Elite?
Rotate or sticky?
Is this a Cloudflare or CAPTCHA bypass?
Why did I get a 407 instead of a catalog item?
Best practices for n8n plus residential proxies
- Collect public pages and public APIs only. Do not log in, do not solve CAPTCHAs, and do not treat a proxy as a license to ignore a site's terms, robots.txt, or rate limits.
- Set Options → Proxy to a full HTTP URL with a scheme. URL-encode only the password component when it contains @, :, or /.
- Verify resolved_country (and resolved_city on Elite) through the same Proxy string before the first target request. Store requested_* next to resolved_*. Stop the catalog GET on mismatch.
- Fetch in HTTP Request. Classify in Code. Do not assume the Code sandbox can open sockets — confirm against your n8n deployment.
- Match Accept-Language and site country parameters to the exit country. Contradictory locale is both a bad dataset and a bot tell.
- Rotate per independent URL. Reserve sticky (1–1440 minutes, default 30) for pagination and the identity that holds a cookie; n8n Pagination reuses the Proxy string — it does not create a session.
- On n8n Cloud, use the per-node Proxy field. On self-hosted, if you set HTTP_PROXY, also set NO_PROXY for n8n's own hosts.
- Turn on Include Response Headers and Status and read statusCode from that option. Never Error can keep target 403/429 as items; CONNECT 407 can still fail the node — treat a red node as proxy auth, not a catalog row.
Common mistakes when proxying n8n
- Pasting a proxy host without http:// so the request leaves from the n8n host IP.
- Putting socks5:// in the HTTP Request Proxy field and debugging a 'direct' request as a block.
- Hard-coding geo_ok = true instead of reading $('Check geo').
- Labeling populated from body length > 200 so an error page becomes a catalog row.
- Reusing the editor's US proxy URL on a scheduled DE workflow.
- Using n8n Pagination without a session id, then calling page two empty.
- Treating a red 407 node as the target blocking you.
- Fetching from a Code node instead of HTTP Request when your sandbox does not allow sockets.
- Setting instance HTTP_PROXY and wondering why n8n's own API calls break.
- Confusing N8N_PROXY_HOPS (n8n behind nginx) with the HTTP Request Proxy option.
- Storing a 200 with a consent, challenge, or unclassified body as a catalog row.
