Guide

How to Use Proxies With Axios (Node.js)

How to use proxies with axios: why the proxy option fails on HTTPS, the httpsAgent fix with https-proxy-agent, SOCKS, auth, rotating a pool, timeouts and verifying the exit.

HProxy Team··6 min read
HProxy.Guide

Skip the dead lists.

Our free proxy list re-checks every exit every few minutes across 100+ countries, with a live last-checked time, so you copy IPs that worked moments ago, not a stale text dump.

Open the free proxy list

axios is the most popular HTTP client in Node, and its proxy support has the most notorious gotcha of any client we help people with: the built-in proxy option works for plain HTTP and quietly fails for HTTPS, which is nearly every real target. This guide covers proxies with axios the way that actually holds up in production: why the proxy option breaks on HTTPS, the httpsAgent fix, SOCKS5, authentication, rotating a pool, and the timeout and error handling an unattended scraper needs.

We run a proxy network and a live proxy checker, so the axios proxy report we see most is "it works on http sites and does nothing on https ones." That is not a bug in your code, it is the proxy option doing what it does, and the fix is to stop using it for HTTPS. Every example runs on axios 1.x. Where one needs a live proxy, pull a fresh one from our free proxy API, which returns recently checked endpoints with no key.

The short version

Do not use the axios proxy option for HTTPS. Install https-proxy-agent, pass it as httpsAgent, and set proxy: false so axios does not fight the agent. That one pattern works for HTTP and HTTPS, handles authentication, and is the base for rotation.

import axios from "axios";
import { HttpsProxyAgent } from "https-proxy-agent";

const agent = new HttpsProxyAgent("http://user:pass@203.0.113.7:8080");

const res = await axios.get("https://httpbin.org/ip", {
  httpsAgent: agent,
  proxy: false,          // stop axios's own proxy handling
  timeout: 15000,
});
console.log(res.data);

Why the proxy option fails on HTTPS

The built-in option looks right and works in the first test, because the first test is usually an http:// URL:

// Works for HTTP targets, unreliable for HTTPS
const res = await axios.get("http://httpbin.org/ip", {
  proxy: { host: "203.0.113.7", port: 8080, auth: { username: "user", password: "pass" } },
});

The moment you point it at an https:// URL, axios handles the TLS and the proxy tunnel itself, and for a plain HTTP proxy carrying HTTPS through a CONNECT tunnel it commonly sends the request to the proxy as if it were the origin, or ignores the proxy and goes direct. The symptom is a request that either errors oddly or returns your real IP. Because almost every site you scrape is HTTPS, the practical rule is to treat the proxy option as HTTP-only and reach for an agent everywhere else.

The axios proxy path that actually works
  1. axios proxy option

    fine for http, breaks on https

  2. https-proxy-agent

    handles the CONNECT tunnel

  3. httpsAgent + proxy:false

    axios steps aside

  4. Target over HTTPS

    sees the proxy IP

Source: Why HTTPS needs an agent, not the proxy option

Authentication

With the agent approach, credentials live in the proxy URL as user:pass@host:port, and the agent answers the proxy's challenge for you:

const agent = new HttpsProxyAgent("http://user:pass@203.0.113.7:8080");
await axios.get("https://example.com", { httpsAgent: agent, proxy: false });

If the password contains characters that are special in a URL, percent-encode it first with encodeURIComponent, or the URL parses wrong and you get a confusing failure that looks like a dead proxy. A wrong or missing password comes back as HTTP 407, which at least confirms the proxy itself is alive.

SOCKS5 with socks-proxy-agent

axios has no SOCKS support of its own. Install socks-proxy-agent and pass its agent as both httpAgent and httpsAgent so HTTP and HTTPS both route through it:

import axios from "axios";
import { SocksProxyAgent } from "socks-proxy-agent";

const agent = new SocksProxyAgent("socks5://user:pass@203.0.113.7:1080");

const res = await axios.get("https://httpbin.org/ip", {
  httpAgent: agent,
  httpsAgent: agent,
  proxy: false,
  timeout: 15000,
});

Use socks5h:// (or rely on the agent resolving remotely) to have the proxy perform DNS rather than your machine, which keeps your resolver from seeing every target. We cover that split in what is a SOCKS5 proxy.

Rotating a pool

Because the agent carries the proxy, rotation is choosing which agent to attach and retrying on a different exit when one fails:

import axios from "axios";
import { HttpsProxyAgent } from "https-proxy-agent";

const POOL = [
  "http://user:pass@203.0.113.7:8080",
  "http://user:pass@203.0.113.24:3128",
  "http://user:pass@198.51.100.14:8080",
];

async function getRotating(url, tries = 4) {
  for (let i = 0; i < tries; i++) {
    const proxy = POOL[Math.floor(Math.random() * POOL.length)];
    try {
      const res = await axios.get(url, {
        httpsAgent: new HttpsProxyAgent(proxy),
        proxy: false,
        timeout: 15000,
      });
      return res.data;
    } catch (err) {
      // dead or blocked exit, try the next one
    }
  }
  throw new Error(`all ${tries} proxies failed for ${url}`);
}

The retry has to pick a different exit, because retrying the same dead proxy just wastes the attempt. You do not have to keep the list current by hand; our free proxy API returns a fresh pool you can fetch at startup and split into POOL.

Timeouts and error handling

An unattended axios run needs a timeout, because without one a proxy that accepts the connection and stalls will hang the request on Node's default socket behaviour far longer than you want. Set timeout in milliseconds on every call, and catch the errors proxies actually raise:

try {
  const res = await axios.get(url, { httpsAgent: agent, proxy: false, timeout: 15000 });
  // use res.data
} catch (err) {
  if (err.code === "ECONNREFUSED") {
    // nothing listening: dead proxy or wrong port
  } else if (err.code === "ECONNABORTED") {
    // the timeout tripped: proxy connected then stalled
  } else if (err.response && err.response.status === 407) {
    // proxy alive, credentials wrong
  } else {
    // ETIMEDOUT, socket hang up, TLS errors, and so on
  }
}

ECONNREFUSED and ECONNABORTED are the two you will see most: the first is a dead exit, the second is your timeout doing its job on a slow one. Both are cues to rotate to the next proxy rather than retry the same one.

Verify the exit IP

Confirm the proxy took effect instead of trusting that it did:

const real = (await axios.get("https://httpbin.org/ip", { timeout: 10000 })).data.origin;
const viaProxy = (await axios.get("https://httpbin.org/ip", {
  httpsAgent: new HttpsProxyAgent(POOL[0]),
  proxy: false,
  timeout: 15000,
})).data.origin;

console.log("real:", real, "proxy:", viaProxy);
if (real === viaProxy) throw new Error("proxy is not changing your IP");

If the two match on an HTTPS request, you are almost certainly still using the proxy option somewhere instead of httpsAgent.

Where to go from here

axios is a great client with one sharp edge, and once the httpsAgent pattern is muscle memory the proxy part is boring, which is what you want. The rest is the discipline every scraper needs: verify each exit, bound concurrency, and rotate a pool that is alive.

For verifying exits, our proxy checker reports exit IP, anonymity grade, geolocation and latency in one paste. For the pool, the free proxy API hands you recently checked endpoints with no key. The Node.js proxy guide covers the native fetch and http paths if part of your code is not on axios, proxies for web scraping covers choosing the right proxy type, and how to avoid IP bans while scraping is the prevention checklist for when rotation is not enough. When a hand-rolled pool becomes a chore, a rotating gateway that returns a fresh residential IP per request at $0.44/GB lets you drop the agent-swapping and keep one endpoint.

Frequently asked questions

Why does the axios proxy option not work for HTTPS?
axios routes HTTPS requests through its own proxy handling, and for a plain HTTP proxy tunnelling HTTPS it frequently sends the request to the wrong place or ignores the setting. The reliable fix is to stop using the proxy option for HTTPS and pass an agent instead: httpsAgent: new HttpsProxyAgent('http://user:pass@host:port') together with proxy: false so axios does not also try to handle it.
How do I set a proxy in axios?
For a quick HTTP target, the built-in option works: axios.get(url, { proxy: { host, port, auth: { username, password } } }). For HTTPS targets, which is almost everything, use the https-proxy-agent package and pass it as httpsAgent with proxy set to false. The agent approach is the one to standardise on because it behaves the same for HTTP and HTTPS.
How do I use a SOCKS5 proxy with axios?
axios has no built-in SOCKS support, so install socks-proxy-agent and pass its agent as both httpAgent and httpsAgent: const agent = new SocksProxyAgent('socks5://user:pass@host:1080'). Use socks5h in the URL, or the agent's default, to resolve DNS on the proxy rather than locally, which is the private choice for scraping.
How do I fix ECONNREFUSED or 407 with an axios proxy?
ECONNREFUSED means nothing is listening at that host and port, so the proxy is dead or the port is wrong. HTTP 407 Proxy Authentication Required means the proxy is alive but the credentials are missing or wrong, which on the agent approach means fixing the user:pass in the proxy URL. Test the same request with no proxy to confirm the proxy is the problem, then swap in a different exit.
How do I rotate proxies with axios?
Build a fresh agent per proxy and pass it on each request, picking a new exit per attempt and retrying on a different one when a request fails. Because the agent carries the proxy, rotation is just choosing which agent to attach, and on any pool some exits are always down so the retry-on-a-different-exit loop is not optional.

Get proxies that are alive right now

Our free list re-checks every exit every few minutes and shows a last-checked time, so you copy IPs that worked moments ago, not a stale text dump. When the location has to survive a real check, the paid network holds up.

129M+ proxy checks run · 100+ countries · HTTP / HTTPS / SOCKS · re-checked every few minutes · no signup