Short answers
Where do I set a proxy for the HTTP Request node?
Try the node first: open Options on the HTTP Request node and look for a proxy field, which takes a full proxy URL including credentials. If your version does not have one, or the node ignores it, set the proxy for the whole n8n process with environment variables instead.
Which environment variables does n8n need?
HTTP_PROXY, HTTPS_PROXY and NO_PROXY, set on the n8n process itself. An https:// target follows HTTPS_PROXY, and its value still points at our HTTP port 7000: the scheme in the variable describes the proxy, the name of the variable describes the target.
Why does nothing change after I export the variables?
Because you exported them in your shell and n8n is running somewhere else: in a container, under systemd, or as a service started at boot. The process that opens the socket has to have the variable, so set it where that process is defined and restart it.
Can I do this on n8n Cloud?
The process-level route needs control of the environment, which self-hosting gives you and a managed instance generally does not. On a managed instance you are limited to whatever proxy field the node itself exposes.
How do I choose the country per workflow?
In the proxy username, not in n8n. USERNAME-peer-es-rot-ondemand is Spain on the residential pool with a new exit per connection; change the two-letter code and you have another country, on the same credential and the same balance.
My execution log shows 407. What now?
Read the gateway code: E_AUTH_INVALID points to credentials; E_CAP_EXCEEDED points to exhausted or expired traffic. Malformed username syntax is documented as 400 E_USERNAME_PARSE. Use the 407 checklist before replacing a password.
Two places to set it
A proxy field on the node is per request and easy to see, which is a real advantage in a workflow other people will edit. Its weakness is that it is one field: everything, including the password, has to fit into a single URL.
The environment variables are coarser and more reliable. They apply to the whole process, so every node that makes an outbound request goes through the gateway, and nothing depends on which node version a workflow happens to use.
The URL n8n needs
One credential serves every country and rotation mode, because the targeting is in the username. Pool and country are positional; rot and sid come after them.
Percent-encode the password if it contains a reserved character. A colon, an at sign or a slash inside a URL ends the field early and the gateway receives a truncated string.
http://USERNAME-peer-es-rot-ondemand:pak_xxx@gw.portproof.org:7000
socks5h://USERNAME-peer-es-rot-ondemand:pak_xxx@gw.portproof.org:7001
# USERNAME the account part of your username, from the dashboard
# peer the pool: peer residential, mbl mobile 4G/5G
# de the country, two letters, lower case
# rot-... ondemand, auto5 to auto60, or sticky with a sid
# pak_xxx the password, percent-encoded if it contains : @ / ? or #
# gw.portproof.org the gateway host: 7000 for HTTP, 7001 for SOCKS5For a workflow that has to keep one exit across several nodes, add a session: sid-<name> with rot-sticky, one name per run. What that does and does not hold is in why a sticky session changed its IP.
Docker and compose, where most people are stuck
Self-hosted n8n usually runs in a container, and a container inherits nothing from your shell. Put the variables in the run command or the compose file, then recreate the container: a restart alone does not pick up new environment values.
Keep localhost in NO_PROXY so n8n’s own webhook calls and health checks do not leave through the gateway and cost GB.
# docker run
docker run -d --name n8n -p 5678:5678 \
-e HTTP_PROXY="http://USERNAME-peer-es-rot-ondemand:$PROXY_PASSWORD@gw.portproof.org:7000" \
-e HTTPS_PROXY="http://USERNAME-peer-es-rot-ondemand:$PROXY_PASSWORD@gw.portproof.org:7000" \
-e NO_PROXY="localhost,127.0.0.1,n8n" \
-v n8n_data:/home/node/.n8n docker.n8n.io/n8nio/n8n
# docker-compose.yml
services:
n8n:
image: docker.n8n.io/n8nio/n8n
ports: ["5678:5678"]
environment:
HTTP_PROXY: "http://USERNAME-peer-es-rot-ondemand:${PROXY_PASSWORD}@gw.portproof.org:7000"
HTTPS_PROXY: "http://USERNAME-peer-es-rot-ondemand:${PROXY_PASSWORD}@gw.portproof.org:7000"
NO_PROXY: "localhost,127.0.0.1,n8n"
volumes: ["n8n_data:/home/node/.n8n"]
volumes: { n8n_data: {} }
# then: docker compose up -d --force-recreateUnder systemd rather than Docker, the same values go in a drop-in file with Environment=, followed by systemctl daemon-reload and a restart. The wider version of this problem, across curl, Python, Node and Go, is in proxy works in curl but not your app.
One workflow that confirms the exit first
Before the real job runs, prove where requests leave from. One HTTP Request node pointed at the echo endpoint is enough, and it costs a few hundred bytes.
- Add an HTTP Request node, method GET, URL set to the echo endpoint printed on the setup docs.
- Execute the node on its own and read the output: it reports the IP address the request arrived from, not a country, so look that address up if the job depends on the market.
- Run it a second time. With
rot-ondemandthe exit should differ; withrot-stickyand a session name it should not. - Only then wire the node into the workflow that needs the country.
If curl works here and the node does not, the credential is fine and the problem is in how n8n was configured.
curl -s -x "http://gw.portproof.org:7000" \
--proxy-user "USERNAME-peer-es-rot-ondemand:$PROXY_PASSWORD" https://api.portproof.org/v1/echo-ip
# and from our side: the exit IP and the latency (no country), up to six times a minute
curl -X POST https://api.portproof.org/v1/traffic/test \
-H "Authorization: Bearer $PORTPROOF_API_KEY" -H "Content-Type: application/json" \
-d '{"pool":"residential","country":"ES","rotation":"ondemand"}'Reading the execution log
| In the log | Where it came from | What to do |
|---|---|---|
407 with E_AUTH_INVALID | The gateway refused the credentials | Check encoding, the username tokens and the password |
407 with E_CAP_EXCEEDED | The GB balance is used up | Top up; the credentials do not change |
400 with E_USERNAME_PARSE | The username is malformed | Lower case only, no hyphen inside a value |
502 with E_NO_STOCK_COUNTRY | No device online in that country and pool | Retry with backoff, or use the other pool |
| The request succeeded from your own address | n8n never used the proxy | The process did not get the variables; recreate the container |
The whole error table is on the setup docs, and tunnel-level failures have their own page in fix CONNECT tunnel failed errors.
What a workflow costs
Every byte through the gateway is metered in both directions, so a workflow that fetches JSON costs very little and one that renders pages costs a great deal more. A scheduled workflow is worth sizing before you turn it on: runs per day, requests per run, bytes per request.
Both pools bill from one balance: mobile 4G/5G and residential, on the ladder shown on pricing.
| Order size | Per GB | Saving |
|---|---|---|
| 1 to 4 GB | EUR 4.50 | |
| 5 to 24 GB | EUR 3.90−13 % | |
| 25 to 49 GB | EUR 3.50−22 % | |
| 50 to 99 GB | EUR 3.20−29 % | |
| 100 to 249 GB | EUR 2.90−36 % | |
| 250 GB and more | EUR 2.50−44 % |
GB never expire. What you buy stays on your balance until you use it; a new purchase adds to the same balance. The trial is 0.5 GB for EUR 2.90, once per customer.
No fee per connection, per session or per workflow: the meter counts bytes and nothing else.
What is not allowed
What is not allowed: automation does not change the rules. Account creation and farming, credential stuffing, sneaker and ticket bots, and anything else on the declined list are refused whether a person or a workflow issues the request. See the acceptable-use policy.
Read next
Fix 407 Proxy Authentication Required
Read the gateway code, check credentials and balance, then separate a rejected HTTPS tunnel from an HTTP response.
Read the guide
HTTPS_PROXY not working? Check your client
Check inherited variables, exclusions and client support when curl works but an application does not.
Read the guide