🎉 Premium Proxies · 24-Hour Free TrialClaim Now
Use Cases

n8n Proxy Setup: HTTP Request Node With Auth

n8n proxy setup for scraping workflows: the HTTP Request node Proxy field with user:pass, instance-wide HTTP_PROXY in Docker, per-item rotation, EPROTO fixes.

S SparkProxy 1 23 min read
Share

An n8n proxy setup has two possible homes: the Proxy field inside one HTTP Request node, or an HTTP_PROXY variable that covers the whole self-hosted instance. Pick the wrong one and you either leak your scraper's real IP on half the nodes or send your Slack alerts and LLM calls through a residential gateway for no reason. This guide builds a working scraping pipeline around the right choice: the exact URL format the node accepts, why n8n Cloud forces user:pass authentication, how many exit IPs a 500-item run really uses, and what EPROTO ... wrong version number and socket hang up mean when they show up on HTTPS targets.

Everything about n8n internals below was read from the n8n repository at release tag n8n@2.45.7 (published on GitHub on 2026-10-30) and from docs.n8n.io on the same day.

The job: a scheduled scrape that exits through a proxy

The workflow most people are building looks like this:

Schedule Trigger (every 6 h)
  -> Data table "watchlist"      (one row per product URL)
  -> HTTP Request                (GET each URL, Proxy option set)
  -> HTML                        (Extract HTML content: price, title, stock)
  -> Data table "price_history"  (insert one row per item)
  -> error output -> Slack       (only the items that failed)

The HTTP Request node runs once per incoming item. Give it 500 rows and it makes 500 requests, each with the URL from {{ $json.url }}. Only that node needs to leave through the proxy. The data table writes, the Slack alert and any OpenAI or Google Sheets nodes should keep using the server's own connection, because routing them through a scraping gateway adds latency and burns concurrency you are paying for.

So the node-level Proxy option scopes the proxy to the scrape, while the instance-level variable scopes it to everything.

Options > Proxy: the URL format the node accepts

Open the HTTP Request node, click Add option and choose Proxy. In the node source (HttpRequest/V3/Description.ts) the field is a plain string with the placeholder e.g. http://myproxy:3128 and the description "HTTP proxy to use". The docs add that it "takes precedence over global settings", meaning it overrides HTTP_PROXY and friends for that node.

For SparkProxy the value is a single URL with the credentials embedded:

Random Rotation (new exit per connection):
  http://USERNAME:PASSWORD@gateway.sparkproxy.io:11000

Sticky Sessions (same exit for up to 5 minutes):
  http://USERNAME:PASSWORD@gateway.sparkproxy.io:11002

Three details in that string trip people up, and each one comes straight from how n8n parses it.

The scheme must be http:// or https://. The check lives in @n8n/backend-network, in a function called isSupportedProxyUrl. Anything else, including socks5://gateway.sparkproxy.io:13000, is not rejected with an error. n8n logs Ignoring unsupported proxy URL "", falling back to environment proxy settings and sends the request through HTTP_PROXY if one is set, or directly from your server if not. A code comment in the same file says it plainly: the agents "speak the HTTP CONNECT protocol, not SOCKS." So SparkProxy's SOCKS5 port is for other tools; inside n8n, use 11000 or 11002.

http:// means the hop to the proxy is plain HTTP, not that the target is. HTTPS targets still work and stay encrypted end to end. n8n opens a CONNECT tunnel to the gateway over plain HTTP, then negotiates TLS with the target inside it. Writing https://gateway.sparkproxy.io:11000 tells n8n to start a TLS handshake with the gateway itself, which fails. That is the most common cause of the EPROTO error covered later.

Special characters in the password must be percent-encoded. n8n validates the string with the WHATWG URL parser. A password containing @, :, / or # either fails validation (and is silently ignored, as above) or splits in the wrong place. Encode the credentials once and paste the result:

"""Build a proxy URL for the n8n HTTP Request node with safely encoded credentials."""
from urllib.parse import quote
import getpass

HOST = "gateway.sparkproxy.io"
PORTS = {"rotating": 11000, "sticky": 11002}


def proxy_url(user: str, password: str, mode: str = "rotating") -> str:
    port = PORTS[mode]
    return f"http://{quote(user, safe='')}:{quote(password, safe='')}@{HOST}:{port}"


if __name__ == "__main__":
    u = input("SparkProxy username: ").strip()
    p = getpass.getpass("SparkProxy password: ")
    for mode in PORTS:
        print(f"{mode:9} {proxy_url(u, p, mode)}")

One security note: the Proxy field is a node parameter, not an n8n credential. The password sits in plain text in the workflow and in every JSON export you share. If that matters, use the instance-level option below or an IP allowlist. For how the gateway checks that password (the 407 challenge and the Proxy-Authorization header), our explainer on how proxy authentication works has the full exchange.

Free trial

Scraping at scale? Skip the blocks.

Fast, unblockable datacentre proxies with unlimited bandwidth.

Why n8n Cloud needs user:pass, not a whitelist

SparkProxy accepts two kinds of authentication: user:pass in the URL, or an allowlisted source IP with no credentials at all. On a VPS you control, the allowlist is tidy. On n8n Cloud it is the wrong tool, and n8n's own documentation explains why.

The page "Find your IP addresses" (docs.n8n.io, read 2026-10-30) says: "n8n Cloud runs on shared infrastructure. Many instances share each address, so there's no address unique to your instance." It adds that an instance's address "changes only in rare cases where n8n has to move instances between clusters. n8n doesn't send advance notice of these changes."

Put that next to the way proxy allowlisting works and two problems appear:

IssueWhat happens if you allowlist n8n Cloud's egress IP
Shared addressEvery other n8n Cloud instance on that address can use your proxy plan with no password
Address can move without noticeYour scrape starts failing with tunnel errors the day your instance changes cluster
Slot countThe published list has 12 `/28` blocks and 14 single addresses; allowlisting all of it does not fit in a plan's slots (SparkProxy plans include 5 to 25 whitelist IPs)

The first row is the serious one: an allowlist trusts anyone connecting from that address, and on shared egress that includes strangers. On n8n Cloud, use user:pass. The general trade-offs are covered in our post on what IP whitelisting is for proxies; for n8n Cloud the answer is not a trade-off.

Self-hosted n8n on a server with a fixed public IP is the reverse case. There, allowlisting that IP lets you write the Proxy field as http://gateway.sparkproxy.io:11000 with no credentials at all, which also removes the plain-text password problem from the previous section.

Self-hosted Docker: one HTTP_PROXY for every node

On self-hosted n8n you can set the proxy once for the whole process. The deployment environment variables page documents four variables:

VariableWhat the docs say it does
`HTTP_PROXY`"A URL to proxy unencrypted HTTP requests through"
`HTTPS_PROXY`"A URL to proxy TLS/SSL encrypted HTTP requests through"
`ALL_PROXY`Used "when more specific variables (`HTTP_PROXY` or `HTTPS_PROXY`) aren't present"
`NO_PROXY`"A comma-separated list of hostnames or URLs that should bypass the proxy"

Note which variable covers which traffic: HTTPS_PROXY is chosen by the scheme of the target, not of the proxy. A scraper hitting https:// sites needs HTTPS_PROXY set, and its value still starts with http://. Our guide to using proxies with Docker explains the daemon, build and runtime layers; for n8n only the runtime layer matters, so the variables go on the n8n container:

services:
  n8n:
    image: n8nio/n8n
    ports:
      - "5678:5678"
    environment:
      - GENERIC_TIMEZONE=Europe/Berlin
      - TZ=Europe/Berlin
      - HTTP_PROXY=http://USERNAME:PASSWORD@gateway.sparkproxy.io:11000
      - HTTPS_PROXY=http://USERNAME:PASSWORD@gateway.sparkproxy.io:11000
      - NO_PROXY=localhost,127.0.0.1,postgres,api.openai.com,hooks.slack.com,.internal.sparkproxy.io
    volumes:
      - n8n_data:/home/node/.n8n
volumes:
  n8n_data:

Two behaviours from the source are worth knowing before you choose this route.

It is global. At startup, n8n's installGlobalProxyAgent() replaces Node's http.globalAgent and https.globalAgent whenever any proxy variable is set. Every node that speaks HTTP, community nodes included, then exits through the gateway unless its host is in NO_PROXY, which is why the example lists the database, the LLM API and the webhook host.

Case matters in a surprising direction. n8n uses the proxy-from-env package, and the docs warn that "lowercase versions (like http_proxy) have precedence over uppercase variants". A stale lowercase https_proxy left in a base image or a .env file silently wins over the uppercase one you just edited. Run docker compose exec n8n env | grep -i _proxy after every change.

QuestionNode Proxy optionInstance `HTTP(S)_PROXY`
Available on n8n CloudYesNo
ScopeOne nodeEvery HTTP call in the instance
Where the password livesInside the workflow JSONIn the container environment
Different port per workflowYes, Rotation in one and Sticky in anotherNo, one value for everything
Risk of proxying traffic you did not mean toLowHigh until `NO_PROXY` is right

A practical split that works: leave the instance unproxied, put the Proxy option on the scraping nodes only, and on self-hosted boxes with a fixed IP, allowlist it so the option needs no password.

Per-item rotation: what the gateway sees from one run

Port 11000 gives a new exit IP for each new connection to the gateway. So the question for an n8n workflow is how many connections one run opens.

The source answers it. For each request, the HTTP Request node passes its options to setAxiosAgents(), which builds a fresh pair of agents through buildNodeAgents(). For an HTTPS target with an explicit proxy, that agent is a TunnellingHttpsProxyAgent, a subclass of https-proxy-agent (pinned at 7.0.6 in the 2.45.7 workspace catalog). New agent per request means no connection pool shared between items, so each item opens its own CONNECT tunnel and gets its own exit from the rotating port.

Unlike a long-lived script where keep-alive reuses one tunnel for many requests, items and tunnels line up one to one. We read this from the code and have not measured exit counts across a large run, so confirm it with the IP check below.

Two consequences follow:

  • Pagination inside one node is not one session. The node's built-in Pagination option sends page 2, 3 and 4 as separate requests, so on port 11000 they leave from different IPs. Sites that tie a result cursor to the IP will return page 1 again, an empty page or a 403.
  • Redirects can switch IP too. n8n follows redirects with its own beforeRedirect handler, which rebuilds agents for the new location. A login that sets a cookie on the first response and checks it after the redirect is a job for Sticky Sessions.

Batching is your concurrency dial

This is the setting that costs people the most money and the most blocks. In HttpRequestV3.node.ts, the node loops over every item, pushes each request promise into an array and waits with Promise.allSettled. It only pauses when the Batching option is set with a non-zero interval. Without that option, 500 items means 500 requests fired together.

Two limits sit behind that burst. Your proxy plan caps simultaneous connections: on SparkProxy that is 100 threads on Starter, rising to 250, 500 and 1,000 on Core, Boost and Plus (plan table on sparkproxy.io, read 2026-10-30). And the target has a per-IP and per-site tolerance that no number of exits removes. If thread limits are new to you, our guide to concurrent connections in proxies explains how they are counted.

Add Options > Batching and set two fields. The node source gives the defaults once the option is added: Items per Batch 50 and Batch Interval (ms) 1000. Starting points we use, not benchmarks:

TargetItems per BatchBatch Interval (ms)Port
Public product pages, light protection20100011000
Site behind a bot manager5300011000
Paginated search results1200011002
Your own site or a partner API500No proxy

In node JSON, which you can paste straight onto the canvas, the scraping node looks like this:

{
  "name": "Fetch product page",
  "type": "n8n-nodes-base.httpRequest",
  "typeVersion": 4.5,
  "parameters": {
    "url": "={{ $json.url }}",
    "options": {
      "batching": { "batch": { "batchSize": 20, "batchInterval": 1000 } },
      "proxy": "http://USERNAME:PASSWORD@gateway.sparkproxy.io:11000",
      "timeout": 30000,
      "response": { "response": { "neverError": true, "fullResponse": true } }
    }
  },
  "onError": "continueErrorOutput"
}

The timeout is raised from the node's 10,000 ms default because the proxy hop adds time to every request, and slow product pages behind a residential exit regularly need more than ten seconds for headers. neverError with fullResponse keeps a 403 or 429 as data with its statusCode, so the next node can sort blocked items from real failures instead of the whole node stopping on the first bad response.

Sticky Sessions for pagination and logins

Switch the port to 11002 and SparkProxy keeps the same exit IP for up to 5 minutes. Use it when one logical job spans several requests:

  • Paginated search or category listings, fetched with Batching set to 1 so pages go one after another.
  • A login step followed by authenticated page fetches, where the session cookie is bound to the IP that received it.
  • Sites that issue a challenge cookie on the first request and expect it back from the same address.

The Sticky window is a clock, not a promise per workflow. A run that takes eight minutes will change IP partway through. Keep each sticky job short: split long category crawls into one workflow execution per category, using the Execute Sub-workflow node with one item per call, so each sub-run finishes inside the window.

Because the Proxy option belongs to each node, one workflow can mix both: a sticky node collects product URLs from listing pages, then a rotating node fetches the products at higher concurrency.

Check the exit IP from inside the workflow

n8n shows no "proxy active" indicator, and the unsupported-URL fallback means a typo can send traffic from your own server without any error. Test before scheduling. n8n's IP-addresses page uses https://api.ipify.org?format=json as its example echo service (run by a third party), and the same trick works through a proxy.

Put a Code node in front of an HTTP Request node, generate a handful of items, and point the request at the echo URL with your Proxy option set:

// Code node, mode "Run Once for All Items": emit 10 identical probe items
return Array.from({ length: 10 }, (_, i) => ({
  json: { probe: i + 1, url: 'https://api.ipify.org?format=json' },
}));

Then summarise the HTTP Request output in a second Code node:

// Code node after the HTTP Request: count distinct exit IPs
const ips = $input.all().map((item) => item.json.ip ?? item.json.body?.ip);
const distinct = [...new Set(ips.filter(Boolean))];
return [{ json: { requests: ips.length, distinctExits: distinct.length, exits: distinct } }];

Expect distinctExits close to requests on port 11000 and one IP on 11002. If that one IP is your server's own, the proxy URL failed validation; look for the "Ignoring unsupported proxy URL" warning in the n8n log.

You can test the gateway itself without n8n from the same host:

curl -sS -x "http://USERNAME:PASSWORD@gateway.sparkproxy.io:11000" https://api.ipify.org?format=json

EPROTO, socket hang up and other proxy errors

We reproduced the failure modes on 2026-10-30 with the same libraries and versions n8n 2.45.7 pins (axios 1.20.0, https-proxy-agent 7.0.6, http-proxy-agent 7.0.2) on Node.js 24, against the real gateway.sparkproxy.io. This was a standalone script using n8n's agent wiring, not a full n8n instance, so the n8n UI may wrap the messages with its own text. Work down the table, cheapest check first:

Error textCauseCan a proxy change fix it?
`getaddrinfo ENOTFOUND gate.sparkproxy.io`Wrong proxy hostnameYes: use `gateway.sparkproxy.io`
`write EPROTO ... SSL routines:tls_validate_record_header:wrong version number`Proxy URL starts with `https://` but the gateway port speaks plain HTTPYes: change the scheme to `http://`
`Proxy connection ended before receiving CONNECT response`Pointed an HTTP proxy URL at a port that is not an HTTP proxy (we hit it with SOCKS5 port 13000)Yes: use 11000 or 11002
`stream has been aborted` (code `ERR_BAD_RESPONSE`) on an `https://` targetGateway refused the `CONNECT` with 407: wrong password, or an IP not on the allowlistYes: fix credentials or allowlist
`407 Proxy Authentication Required` on an `http://` targetSame refusal, but visible as a status because plain HTTP is not tunnelledYes: fix credentials
`403` or `429` from the targetThe site blocked or rate-limited the requestSometimes: slow Batching first, then rotate

The fourth row is the confusing one. On HTTPS targets the 407 never reaches the node as a status code, because it arrives during the tunnel setup. You see an aborted stream instead, and curl in the same situation prints curl: (56) CONNECT tunnel failed, response 407. When a plain-HTTP target does return 407, n8n's error map in node-api.error.ts has no entry for it and falls back to the generic 4XX text "Your request is invalid or could not be processed by the service".

EPROTO in older threads. The n8n community thread "Error when using a proxy in the HTTP request node" (opened June 2022) reports write EPROTO ...:error:1408F10B:SSL routines:ssl3_get_record:wrong version number, the OpenSSL 1.1 wording of the same scheme mismatch. If you see either version, check the scheme first.

socket hang up. This is Node's message for a connection the other side closed before sending a response (code ECONNRESET), and n8n maps ECONNRESET to "The connection to the server was closed unexpectedly, perhaps it is offline." With a proxy in the path, suspect a firewall between n8n and port 11000 or a target dropping the connection after TLS. Older n8n versions had a structural reason too: in a 2023 thread ("HTTP node proxy got error", tested on n8n 0.236.0 and 1.1.1), HTTPS targets failed through an HTTP proxy while HTTP targets worked, and an n8n team member attributed it to axios not supporting HTTPS over an HTTP proxy. The current code routes HTTPS through https-proxy-agent with a real CONNECT tunnel, so if you are on a 2023-era build and see this pattern, upgrading is the fix, not a different proxy.

What a proxy cannot fix: a target that blocks by TLS or browser fingerprint, a page that needs JavaScript to render its prices (the HTTP Request node never runs scripts), and a site where your request pattern, not your IP, is what gets flagged. For the JavaScript case, see the Scraping API section below.

One setting that does not help: Ignore SSL Issues. In proxied-agents.ts the proxy connection only borrows connection options such as keep-alive and timeouts. TLS options, including accepting an unverifiable certificate, apply to the target inside the tunnel. A comment there says the proxy's certificate "is always verified against the proxy hostname". Toggling Ignore SSL Issues will not make an https:// proxy URL work.

Retries, error branches and storage

n8n's Retry On Fail node setting reruns the whole node, not the failed item. In workflow-execute.ts (2.45.7) the retry loop calls runNode() again with the full input, the try count is clamped between 2 and 5 (default 3) and the wait between 0 and 5,000 ms (default 1,000). So if 12 items of a 500-item scrape hit a 429, Retry On Fail re-requests all 500, right when the target is pushing back.

A better pattern for scraping:

  1. Set the HTTP Request node's On Error to Continue (using error output), and turn on Never Error with full response as in the JSON above.
  2. After the node, an If node splits on {{ $json.statusCode }}: 200 goes to parsing, 403/429 go to a retry path, anything else goes to an alert.
  3. The retry path is a Wait node (60 seconds is a reasonable start) and a second HTTP Request node with a smaller batch and, for persistent 403s, the Sticky port.
  4. Items that fail twice go to a "failed_urls" data table with the status and timestamp, so the next scheduled run can pick them up.

For storage, n8n's built-in data tables are fine for a modest watchlist. The docs state that "by default, the total storage used by all data tables in an instance is limited to 200 MiB", configurable on self-hosted through N8N_DATA_TABLES_MAX_SIZE_BYTES. A price history that grows by thousands of rows a day belongs in Postgres instead, written with the Postgres node, and that host goes in NO_PROXY if you use the instance-wide variables.

The Schedule Trigger interval sets daily volume: a 500-URL watchlist every 6 hours is 2,000 requests a day, which matters to the target but not to an unlimited-bandwidth bill.

Calling the Scraping API from the same node

When a target needs JavaScript or keeps blocking the proxy route, swap the proxy for the SparkProxy Scraping API in the same HTTP Request node. Remove the Proxy option and call the API instead:

  • URL: https://scrape.sparkproxy.io/api/v1
  • Send Headers: X-API-Key with your key (store it as a Header Auth credential, which keeps it out of the workflow JSON)
  • Send Query Parameters: url = {{ $json.url }}, plus the options below

Cost per request depends on the parameters. render_js defaults to true and costs 5 credits; render_js=false is a plain fetch at 1 credit. country_code adds 5 credits. New accounts get 1,000 free credits, so a 1-credit plain fetch covers 1,000 test requests, and a rendered page with a country set (5 + 5) covers 100.

If you want metadata as well as the page, add json_response=true. The response is then an envelope with job_id, status_code, duration_ms, credits_used and body, and the body is base64-encoded. Decode it before the HTML node or any text matching, or your selectors will find nothing:

// Code node after a Scraping API call made with json_response=true
return $input.all().map((item) => {
  const env = item.json;
  const html = Buffer.from(env.body, 'base64').toString('utf8');
  return {
    json: {
      job: env.job_id,
      status: env.status_code,
      credits: env.credits_used,
      ms: env.duration_ms,
      html,
    },
  };
});

Do not confuse that envelope with format, which only converts the content (html, md, mdx, json, screenshot, pdf). And if you use extract_rules, the response is replaced by an extracted object, so do not combine it with json_response.

What the pipeline costs to run

n8n's side: self-hosted Community edition runs on your own server. n8n Cloud's pricing page (read 2026-10-30) lists Starter at 20€/month and Pro at 50€/month, both billed annually, with 2,500 and 10,000 workflow executions respectively. One scheduled run of the workflow above is one execution however many items it processes, so a 6-hourly schedule uses about 120 executions a month.

SparkProxy's side: "Starting from $75/month" (USD, prices exclude VAT/GST, checked 2026-10-30). Each plan is a pack defined by its thread count, and bandwidth is unlimited on all of them. For n8n that billing model has a specific consequence. Fetching full product pages, retrying blocked items, and switching to larger pages never changes the bill. What you size the plan by is the peak burst, which in n8n is the Items per Batch value of your busiest node, summed across workflows that can run at the same time.

PlanUSD/monthThreadsWhitelist IPs
Starter751005
Core14025010
Boost24050015
Plus4401,00025

Paying for a longer term cuts the price: 3 months saves 5%, 6 months 10%, 12 months 15%. A single scraping workflow with Items per Batch at 20 uses a fifth of Starter's threads. There is a 24-hour free trial with no card required, long enough to run the exit-IP check, a full batch against your real target and a forced EPROTO by switching the scheme, so you recognise it later.

Frequently asked questions

FAQ

Open the node, choose Add option, then Proxy, and enter one URL such as http://USERNAME:PASSWORD@gateway.sparkproxy.io:11000. The node-level value overrides any HTTP_PROXY set on the instance.

The proxy URL almost certainly starts with https:// while the gateway port speaks plain HTTP. Change the scheme to http://. HTTPS targets still stay encrypted, because n8n tunnels them with CONNECT.

Not safely. n8n documents that Cloud outbound addresses are shared by many instances and can change without notice, so an allowlist would let other tenants use your proxy and could break without warning. Use user:pass in the Proxy field.

No. n8n 2.45.7 accepts only http:// and https:// proxy URLs. A socks5:// value is ignored with a log warning and the request falls back to environment proxy settings or a direct connection.

With a rotating gateway port, yes in practice. The node builds a new proxy agent for each request, so every item opens its own CONNECT tunnel and gets a new exit. Pagination pages and redirects count as separate requests too.

Add HTTP_PROXY and HTTPS_PROXY as environment variables on the n8n container, both with an http:// proxy URL, and list internal hosts and APIs in NO_PROXY. This proxies every node in the instance, so it is only available self-hosted.

Special Discount · 20% off

Get 20% off your first month

Premium datacentre proxies with unlimited bandwidth. Use the code at checkout.

Save up to 15% more on quarterly, half-yearly and yearly plans

Claim Discount

About the Author

This guide was written by the SparkProxy Technical Team, the engineers who operate SparkProxy's gateway (1M+ premium IPs in 80+ countries, HTTP, HTTPS and SOCKS5, user:pass or IP whitelist auth; Random Rotation on port 11000, Sticky Sessions on 11002) and the SparkProxy Scraping API. This guide comes from reading the n8n source our customers' workflows run on, and from reproducing the errors they send to support.

Keep reading

Related articles