A workflow that collects data works perfectly for a week and then starts returning empty arrays. Nothing changed on your side. The HTML the HTTP Request node gets back is a challenge page, or a login wall, or a 200 with nothing in it.
What changed is on the other side, and the reason it happened to you and not to your colleague running the same workflow locally is simple: your n8n instance has one outbound IP address, and every request in every workflow leaves from it. Fifty schedules hitting the same site from one address in a datacenter range is a pattern, and sites have been sorting traffic by that pattern for a long time. It is not personal and it is not a bug. It is the consequence of a server-shaped IP doing browser-shaped things.
The fix is one field in a node you already use.
Where the Setting Is
Open any HTTP Request node, scroll to the bottom, and select Add Option → Proxy. It is a single string field, and it takes a full URL with credentials in it:
http://username:password@host:port
For Evomi's residential network that is:
http://YOURUSER:YOURPASS@rp.evomi.com:1000
The username is your account username, on its own — no prefix, nothing prepended. Getting that wrong is the most common first failure, and it is not obvious from the response: the proxy answers 407 Proxy Authentication Required, which reads like a wrong password.
Port 1000 is HTTP, 1001 is HTTPS and 1002 is SOCKS5, all on the same host with the same credentials. Use 1000 unless you have a specific reason not to — it handles HTTPS targets perfectly well, because the proxy is what you are connecting to over port 1000, not the target. Port 1001 means connecting to the proxy itself over TLS, which is slower and rarely what anyone actually wants.
Other products are the same shape with a different host and port: datacenter on dcp.evomi.com:2000, mobile on mp.evomi.com:3000.
That is the whole setup. Everything below is about controlling which IP you get.
Keeping the Password Out of the Workflow
The Proxy field is plain text, and n8n has no dedicated proxy credential type, so the naive version puts a live password into a workflow that gets exported, shared and committed.
Two ways out, depending on where you run.
Self-hosted: use an environment variable. Expressions can read the process environment through $env:
{{ 'http://' + $env.EVOMI_USER + ':' + $env.EVOMI_PASS + '@rp.evomi.com:1000' }}
Now the credential lives in your Docker environment or secrets manager, and the exported workflow JSON contains an expression rather than a password.
You almost certainly have to turn this on first, and the documentation reads as though you do not. n8n's environment variable reference lists N8N_BLOCK_ENV_ACCESS_IN_NODE with a default of false, which sounds like access is allowed unless you disable it. What the code actually does is treat any value other than the literal string false as blocking — including the variable being absent. So a stock instance denies it. The expression above fails with:
access to env vars denied
The fix is to set it explicitly, which is a one-line change and is worth doing at the same time as you put the credentials in:
services:
n8n:
image: docker.n8n.io/n8nio/n8n
environment:
- N8N_BLOCK_ENV_ACCESS_IN_NODE=false
- EVOMI_USER=your-username
- EVOMI_PASS=your-passwordBe deliberate about it rather than reflexively: it opens the whole process environment to anyone who can edit a workflow on the instance, which on a shared instance is a different decision than on your own.
Cloud, or anywhere you would rather not use $env: build the string in a Set node at the top of the workflow, reference an n8n Variable if your plan has them, and pass the result down. It is not better security so much as fewer copies of the same secret: one node to change instead of eleven.
Either way, rotate the password if it has ever been in an exported workflow. Workflow JSON travels — into git, into support tickets, into screenshots.
Targeting: Country, City, Region, ISP
This is the part that is unusual and worth understanding, because it is not where most providers put it. Targeting parameters go in the password field, appended to your password, not in the hostname and not as headers. One endpoint, and the credential string decides where you come out.
Where you want to come out | Proxy URL |
|---|---|
Anywhere |
|
Germany |
|
Berlin specifically |
|
Country codes are ISO 3166-1 alpha-2, and some full names are accepted too (_country-UnitedStates). City names are lowercased with spaces replaced by dots, so New York is _city-new.york. Region and ISP work the same way, as _region- and _isp-.
What this means practically in n8n is that switching country is a data change, not a configuration change. You can drive it from the items flowing through the workflow:
{{ 'http://USER:PASS_country-' + $json.country + '@rp.evomi.com:1000' }}
A Split In Batches loop over [{country: 'DE'}, {country: 'FR'}, {country: 'JP'}] now collects the same page from three countries with one HTTP Request node and no branching. For anything price-, availability- or ranking-related that is the difference between one workflow and three. The countries, regions, cities and ISPs available to you are listed in the dashboard's Proxy Generator, and there is a browsable list on the locations pages.
Sessions: When You Need the Same IP Twice
By default every request through the residential endpoint leaves from a different IP. That is what you want when you are collecting many independent pages. It is exactly wrong when the second request depends on the first — a login, a cart, a paginated result set tied to a server-side cursor.
Two parameters hold an address, and the difference between them matters:
Parameter | Behaviour |
|---|---|
| Holds one IP, tuned for overall success rate. May move you to a different IP to keep the connection working. Accepts |
| Holds the exact IP for as long as the network can. Ignores |
_lifetime- is in minutes: 30 by default, 1440 maximum. Treat that ceiling as a real constraint rather than something the network will police for you — the documentation says an over-limit value returns 412, but requests asking for 1441 and for 99999 both connected normally in testing, so a typo here buys you a session that quietly expires at 24 hours instead of an error that tells you why.
What you want | Proxy URL |
|---|---|
A session held for ten minutes, from a US IP |
|
The same IP for as long as it lasts |
|
Pick session for anything where getting the data matters more than the address staying identical — that is most collection work. Pick hardsession where a change of address logs you out, because there the failure is not a retry, it is starting over.
Generating the session key per workflow execution rather than hardcoding it keeps concurrent runs from sharing an IP:
// Code node, before the loop
return [{
json: {
session: Math.random().toString(36).slice(2, 10)
}
}];Forcing a New IP Mid-Workflow
A held IP that stops working is the one situation the password string cannot fix, because the session key is the thing keeping you on the bad address. Changing the key means a new session, and any server-side state attached to the old one is gone.
The rotation API solves it: same session ID, new IP.
Add an HTTP Request node before the retry:
Field | Value |
|---|---|
Method | GET |
URL |
|
Query: |
|
Query: |
|
Header: | Your Public API key — not the Scraper API key, they are different |
product is one of rpc, rp, sdc or mp depending on which network the session belongs to. A success looks like:
{ "success": true, "message": "Session reset successfully" }The old IP is released immediately, a new one comes from the same geographic pool, and the session ID and credentials are unchanged. Wire it into the error output of your scraping node and you have a workflow that recovers instead of failing.
One thing not to do: rotate before every request. It costs a round trip, it throws away a working connection, and a client that presents a new address for every single call is itself a pattern. Rotate on failure, or every N iterations in a long loop — not reflexively.
The Environment Variables That Override Everything
n8n also honours the standard proxy environment variables, and this is where the surprising failures live.
Variable | Effect |
|---|---|
| Proxies all unencrypted HTTP traffic from nodes |
| Proxies all TLS traffic from nodes |
| Used when neither of the more specific two is set |
| Comma-separated hosts to connect to directly |
Three things about these are worth knowing before you set one.
They are instance-wide. Every node, every workflow, every outbound call — including n8n's calls to services that will be confused by a residential IP. This is a blunt instrument, and it is almost never what you want for collection work.
The per-node Proxy field wins. Set HTTPS_PROXY to a dead address and two HTTP Request nodes on the same canvas behave differently: the one with a Proxy option set returns normally through that proxy, and the one without it fails with connect ECONNREFUSED. That is the behaviour you want — override where you mean to, leave everything else alone — and it is worth knowing it holds, because the alternative would make the per-node field useless on any instance with a corporate proxy configured.
Lowercase wins. n8n uses the proxy-from-env package, which gives http_proxy precedence over HTTP_PROXY when both are set. If someone set one of each months ago, the one you are reading is not necessarily the one in effect.
NO_PROXY is your escape hatch. If you do set an instance-wide proxy, put your internal hosts and your own APIs in NO_PROXY before you find out the hard way which of them stopped working.
While you are in the environment file, there is one more pair worth knowing about, because changing the IP does nothing if the request announces itself as a robot anyway. n8n sends a bare n8n User-Agent by default:
N8N_ENFORCE_GLOBAL_USER_AGENT=true
N8N_GLOBAL_USER_AGENT_VALUE=YourCompanyBot/1.0 (+https://yourcompany.com/bot)The first replaces the bare string with an RFC-compliant Mozilla/5.0 (compatible; n8n/<version>; +https://n8n.io/) on every outbound request, which n8n documents specifically as a way to stop WAFs rejecting them. The second overrides it with your own value, so you can identify yourself rather than disclose your n8n version to every server you touch. Unlike the proxy variables, these two are safe to set instance-wide — and an honest User-Agent with a contact URL prevents a category of blocks that no amount of IP rotation will.
Choosing the Right Network
The Proxy field does not care which product you point it at, so the choice is about the target rather than the configuration.
Network | Rate | Fits |
|---|---|---|
from $0.30/GB | APIs, tolerant sites, high volume where cost per GB dominates | |
from $0.49/GB | Consumer sites, region-specific content, anything sorting traffic by network type | |
from $2.20/GB | Mobile apps and sites that treat carrier IPs differently |
The honest sequencing is to start on datacenter and move up only when you have to. Datacenter is cheaper and faster, and a target that does not care will never make you pay for residential. A target that does care will tell you within about ten requests. The difference between the two is not quality — it is who owns the address block, and whether a site can filter it wholesale without losing real visitors.
Two Node Settings That Matter More With a Proxy
Timeout. The default is generous, and a proxied request that has already failed is just holding a worker. Under Options → Timeout, 20,000–30,000 ms is a reasonable working range for residential routing — long enough to allow for the extra hop through a home connection, short enough that a dead request does not hold up the queue. The distinction between connect and read timeouts is worth understanding here, because they fail for entirely different reasons.
Batching. Options → Batching gives you Items per Batch and Batch Interval. Both are about the target as much as your instance: fifty simultaneous requests from a rotating pool is a burst, and a burst from many addresses at once is a more distinctive signal than a steady stream from a few. Ten items per batch with a 1,000 ms interval is a sensible starting point that costs you very little in wall-clock time.
What People Get Wrong
Putting the country in the hostname. There is one hostname. Targeting lives in the password.
Reusing one session key across concurrent executions. They share an IP, which is usually the opposite of the intent. Generate it per run.
Setting
HTTPS_PROXYto solve one workflow's problem. It applies to every outbound call the instance makes, including ones you did not think about.Rotating on every request. It is slower, it wastes the session, and the behaviour is itself distinctive.
Using
hardsessioneverywhere. It prioritises keeping the exact address over keeping the connection working. For plain collection that trade goes the wrong way.Leaving the password in the workflow JSON. It travels further than you think.
Assuming a proxy is sufficient. It changes where the request comes from, not what it looks like. Header order, TLS fingerprint and request timing are all still saying "automated", and there are a lot of signals in that stack.
Wrapping Up
One field, one URL, and an n8n instance stops looking like one machine repeatedly asking for the same thing. The parts worth spending time on are the ones after that: which network fits the target, whether a request needs to be tied to the one before it, and what happens when a held address stops working — because that last one is the difference between a workflow that recovers and a workflow that pages you.
The proxy is the connection, not the request. If the fetching itself is the hard part — JavaScript-rendered pages, CAPTCHAs, pages that need a real browser — that is a different tool, and there is a guide to which n8n scraping approach fits which page.
