axios HTTPS proxy not working: httpsAgent, proxy: false and the 407
The worst axios proxy bugs don’t throw. The request just leaves from your own IP, or from a proxy you forgot about. Here is each one, measured.
In short
Put the proxy agent on httpsAgent (not httpAgent) and set proxy: false, so neither axios’s own option nor HTTPS_PROXY takes over. A 403 or 407 can be the proxy refusing you; check err.response.statusText.
We checked every setup against api.ipify.org and compared the answer with our own IP. Two of them never touched the proxy, and neither raised an error:
| Setup | Where the request came from |
|---|---|
| httpsAgent: proxy agent, proxy: false | A residential exit |
| proxy: { protocol: 'http', host, port, auth } | A residential exit |
| httpAgent: proxy agent, proxy: false | Our own IP: the agent was never used |
| httpsAgent: proxy agent, HTTPS_PROXY set, no proxy: false | The HTTPS_PROXY proxy (a 407 with a stale login) |
| proxy: { protocol: 'https', … } | write EPROTO … wrong version number |
The agent is on httpAgent
axios picks the agent by the scheme of the URL you request: httpsAgent for https://, httpAgent for http://. Almost everything is https:// today, so an agent on httpAgent does nothing and, with proxy: false, the request goes out directly. Your scraper works, from your own address.
HTTPS_PROXY wins
axios reads HTTP_PROXY, HTTPS_PROXY and NO_PROXY unless you pass proxy: false. With the variable set, our agent was bypassed and the request went to the proxy in the variable, whose login was stale: 407. With an agent, always add proxy: false.
protocol: 'https'
The protocol in axios’s proxy option is how axios talks to the proxy, not to the website. Our proxy port speaks plain HTTP, so 'https' sent a TLS handshake to it and Node failed with EPROTO … wrong version number. The same error in other languages.
When the proxy refuses the tunnel, axios turns its answer into an ordinary AxiosError with that status, both with the built-in option and with https-proxy-agent. It reads exactly like the website refusing you:
Proxy or website?
Look at the response. Our gateway’s 403 had statusText “Invalid Request”, a body naming the bad part (Invalid "country") and a single content-length header. A website’s 403 came with its own date, server and content-type headers. 465 is never a website: it is our gateway saying no exit matches your filters.
A key the proxy doesn’t know
-cc-DE, the way some other gateways write a country, isn’t a key on ours. It was ignored and the request came back 200 from Australia. Write -country-de; the other targeting options are on the locations page.
Our rotating port picks a new exit per connection. Whether you get a new IP per request therefore depends on whether the agent reuses its connection:
For the same IP on purpose, for a login or a checkout, add a session label and use the sticky port 10000: USER-country-de-session-checkout1-lifetime-10.
A setup that works
An axios instance per identity, the agent on httpsAgent, proxy: false, and a retry for dropped exits. Ran as shown.
import axios from "axios";
import { HttpsProxyAgent } from "https-proxy-agent";
const USER = process.env.PROXY_USER;
const PASS = process.env.PROXY_PASS;
// One agent = one kept connection = one IP. Make a new agent for a new IP.
function proxied(targeting = "") {
const login = `${encodeURIComponent(USER + targeting)}:${encodeURIComponent(PASS)}`;
return axios.create({
httpsAgent: new HttpsProxyAgent(`http://${login}@via.proxies.vision:9000`), // httpsAgent, not httpAgent
proxy: false, // ignore HTTPS_PROXY and axios's own proxy option
timeout: 30000,
});
}
// Residential exits sometimes drop a connection (ECONNRESET): retry on a new one.
// A status from the proxy (407, 403, 465) won't change on a retry.
async function get(url, targeting = "", attempts = 3) {
for (let i = 1; ; i++) {
try {
return await proxied(targeting).get(url);
} catch (err) {
if (err.response || i === attempts) throw err;
}
}
}
for (let i = 0; i < 3; i++) {
const { data } = await get("https://ipinfo.io/json", "-country-de");
console.log(data.ip, data.country, data.org);
}79.220.1.67 DE AS3320 Deutsche Telekom AG
92.208.79.60 DE AS3209 Vodafone GmbH
212.114.229.147 DE AS8767 M-net Telekommunikations GmbHWhy the retry
In our first run the second request failed with ECONNRESET, “Client network socket disconnected before secure TLS connection was established”: the exit, someone’s home connection, went away. A new agent got a new exit and the request went through. Errors without err.response are worth a retry; a status from the proxy isn’t.
Why does axios ignore my proxy agent?
Do I still need https-proxy-agent with axios?
Why do I need proxy: false when I use an agent?
Is 'Request failed with status code 403' from the website or from the proxy?
What does 'write EPROTO … wrong version number' mean with an axios proxy?
Keep reading
Node.js fetch With a Proxy: undici ProxyAgent, NODE_USE_ENV_PROXY and Errors
Node's fetch ignores HTTPS_PROXY. Three ways to proxy it, the undici 8 'invalid onRequestStart method' trap, and where the proxy's 407 hides.
6 min read · #node.js#errors
Java Proxy Authentication: 407 With the Right Password (disabledSchemes)
Why Java's HttpClient and HttpURLConnection get 407 from a proxy even with the correct login, and the one property that fixes it.
5 min read · #errors#authentication
aiohttp Proxy Errors: ClientHttpProxyError 407, SOCKS5 and trust_env
aiohttp's ClientHttpProxyError explained, why its message leaks your proxy password, socks5:// without aiohttp-socks, and rotation.
6 min read · #python#errors
Residential proxies, pay per GB
HTTP and SOCKS5. Country, city and sticky sessions in the username. No subscription.