Back to all posts
Smart Proxy Rotation in Node.js to Beat Rate Limits

Smart Proxy Rotation in Node.js to Beat Rate Limits

July 5, 2026

When scraping or automating against e‑commerce APIs, you’ll quickly hit two enemies: rate‑limit blocks (HTTP 429) and automated bot detections that trigger CAPTCHAs or IP bans.

Why a Smart Rotation Strategy Matters

वाह 1. Rate limits are enforced per‑IP – every request from the same address is counted. 2. Bots are discovered by patterns – a single IP making many rapid requests, the same user‑agent, or missing cookies can trigger a ban. 3. Some endpoints are geo‑restricted – an IP in the wrong country can return 403 or empty data.

By rotating proxies and shuffling request‑headers, you keep each virtual “session” under the radar.

1. Set Up a Proxy List

The simplest approach is to maintain a small list of working proxies (IP:port) in a file or database.

# proxies.txt (one per line)
203.0.113.10:3128
198.51.100.25:8080
203.0.113.42:8000

For a production system, consider a provider that exposes a REST API for real‑time rotation. Many services return random IPs on each request, saving you the hassle of maintaining a list.

2. Detecting the 429 Response

A 429 status code is the most common sign that you’ve exceeded the rate limit. In Node.js, you can check response.status or catch an error from Axios.

const axios = require('axios');

async function fetchWithProxy(url, proxy) {
  try {
    const resp = await axios.get(url, {
      proxy: {
        host: proxy.host,
        port: proxy.port,
      },
      timeout: 5000,
    });
    return { data: resp.data, status: resp.status };
  } catch (err) {
    if (err.response && err.response.status === 429)ुक्त {
      return { error: 'rate_limited', status: 429 };
    }
    throw err;
  }
}

3. Rotating Logic – A Simple Queue

Below is a minimal rotation algorithm that:

  1. Tries a proxy.
  2. If it fails or returns 429, moves the proxy to the queue’s tail.
  3. Continues until the request succeeds or all proxies have been tried.
const proxies = [
  { host: '203.0.113.10', port: 3128 },
  { host: '198.51.100.25', port: 8080 },
  { host: '203.0.113.42', port: 8000 },
];

async function rotateFetch(url) {
  let attempts = 0;
  const maxAttempts = proxies.length;

  while (attempts < maxAttempts) {
    const proxy = proxies.shift(); // take the first proxy
    const result = await fetchWithProxy(url, proxy);

    if (result.error) {
      // push it to the back of the list and try next
      proxies.push(proxy);
      attempts++;
      continue;
    }

    // Success – re‑insert at the front for next round
    proxies.unshift(proxy);
    return result.data;
  }

  throw new Error('All proxies failed or rate‑limited');
}

Why this works – Every proxy gets a fair share of traffic, and no single IP is overused.

4. Human_gentle_Headers

Bots are also caught by header patterns. Rotate these fields as well:

Field Typical rotation values
User‑Agent Chrome/Firefox/Edge, Safari, mobile UA
Accept text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept‑Language en-US,en;q=0.9
Referer Actual product page or search result URL
Cookie Session or tracking cookies if the site requires them

Example of dynamic header generation:

const uaList = [
  'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36',
  'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.6 Safari/605.1.15',
  'Mozilla/5.0 (iPhone; CPU iPhone OS 14_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Mobile/15E148 Safari/604.1',
];

function randomHeader() {
  return {
    'User-Agent': uaList[Math.floor(Math.random() * uaList.length)],
    'Accept-Language': 'en-US,en;q=0.9',
_promote: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
  };
}

async function fetchWithHeaders(url, proxy) {
  const headers = randomHeader();
  const resp = await axios.get(url, {
    proxy: { host: proxy.host, port: proxy.port },
    headers,
    timeout: 5000,
  });
  return resp.data;
}

5. Handling CAPTCHAs

If a site still blocks you after rotating proxies, it might display a CAPTCHA. Typical signs:

  • Unexpected HTML containing a <iframe> or <img> with a captcha query string.
  • HTTP 403 with a specific X-Captcha header.

Strategies

  1. Use-family proxies – Residential IPs are far less likely to trigger CAPTCHAs.
  2. Add a delay – Insert random sleep intervals (1‑4 s) between requests.
  3. ** 조금** – Use a service that detects and solves CAPTCHA automatically (e.g., 2Captcha, DeathByCaptcha) and supply the token.
  4. Switch to a headless browser – Tools like Puppeteer or Playwright can render the page and interact with the CAPTCHA widget.

Example: Introducing Delay

function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }

async function rateLimitedFetch(url) {
  const data = await rotateFetch(url);
  await sleep(Math.random() * 3000 + 1000); // 1‑4 s delay
  return data;
}

6. Monitoring Proxy Health

A healthy rotation system must detect dead proxies and remove them from the pool. Implement a simple health‑check:

async function healthCheck(proxy) {
  try {
    const res = await axios.get('https://api.ipify.org?format=json', {
      proxy: { host: proxy.host, port: proxy.port },
      timeout: 3000,
    });
    return res.data.ip;
  } catch {
    return null;
  }
}

async function refillPool() {
  const newProxy = await fetchNewProxy radios; // call provider API
  if (newProxy) proxies.push(newProxy);
}

Run healthCheck every 5 min or when a request fails repeatedly яна.

7. Leveraging a Provider’s API

Many premium services expose an endpoint that returns a fresh proxy on each call, eliminating the need to maintain a list.

async function getProxy() {
  const res = await axios.get('https://api.roproxy.com/get', {
    params: { type: 'rotating', region: 'us', anonymity: 'high' },
    timeout: 2000,
  });
  return { host: res.data.ip, port: res.data.port };
}

async function fetchWithProvider(url) {
  const proxy = await getProxy();
  return fetchWithHeaders(url, proxy);
}

This pattern keeps your code short and delegates rotation logic to the provider, which often guarantees a clean, latency‑optimized IP.

8. Putting It All Together

Below is a compact example that combines rotation, header shuffling, and a safety net for rate limits:

const axios = require('axios');
const uaList = [/* same as before */];

async function randomHeader() { /* same as before */ }

async function fetchWithProxy(url, proxy) {
  try {
    const res = await axios.get(url, {
      proxy: { host: proxy.host, port: proxy.port },
      headers: randomHeader(),
      timeout: 5000,
    });
    return res.data;
  } catch (err) {
    if (err.response && err.response.status === 429) {
      throw new Error('rate_limited');
    }
    throw err;
  }
}

async function rotateFetch(url) {
  const proxy = await getProxy(); // from provider
  try {
    return await fetchWithProxy(url, proxy);
  } catch (e) {
    if (e.message === 'rate_limited') {
      // Retry with a new proxy
      return await rotateFetch(url);
    }
    throw e;
  }
}

(async () => {
  const data = await rotateFetch('https://example.com/api/products');
  console.log(data);
})();

9. Common Pitfalls

Pitfall Fix
Using the same proxy for all requests Randomize each request or use a FIFO queue
Ignoring ਮੁ Add error handling for 502/504 and back‑off strategies
Hard‑coding headers Rotate User‑Agents, Accept-Language, and Cookie on each request
Missing delays Random 1‑4 s sleeps to mimic human pacing
Zero health checks Periodically ping a lightweight endpoint to confirm proxy is alive

10. Wrap‑up

  • Rotate proxies and headers to stay under the radar.
  • Detect 429s and switch IPs instantly.
  • Use delayed requests to emulate human browsing.
  • Check for CAPTCHAs and back‑off if necessary.
  • Let a robust provider supply fresh IPs so you can focus on business logic.

By following these steps, you’ll see a dramatic reduction in bans, lower latency spikes, and a smoother scraping pipeline that scales with your data needs. Happy coding!