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

Smart Proxy Rotation in Node.js to Beat Rate Limits

5 July 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!