Home/Blog/Mobile Proxy for Apify
Tool Integration

Mobile Proxy for Apify

Apify's SDK has first-class support for custom proxies via Actor.createProxyConfiguration with a proxyUrls list. Pass in mobile proxy URLs, hand the config to a CheerioCrawler or PlaywrightCrawler, and you're done.

6 min read·Scraping Platform·Last updated: May 2026

Prerequisites

  • Apify CLI (npm i -g apify-cli) and an Actor scaffold (apify create my-actor).
  • Mobile proxy credentials from mobileproxies.org.
  • Apify SDK v3+ (apify@^3, crawlee@^3).

Step-by-Step Configuration

STEP 01

Add credentials to Actor input schema

In INPUT_SCHEMA.json expose fields so they're encrypted on Apify and don't appear in logs:

{
  "title": "Mobile Proxy Scraper",
  "type": "object",
  "schemaVersion": 1,
  "properties": {
    "proxyHost":  { "type": "string", "title": "Proxy host", "default": "proxy.mobileproxies.org" },
    "proxyPort":  { "type": "integer", "title": "Proxy HTTP port", "default": 8000 },
    "proxyUser":  { "type": "string", "title": "Proxy username", "isSecret": true },
    "proxyPass":  { "type": "string", "title": "Proxy password", "isSecret": true },
    "apiKey":     { "type": "string", "title": "mobileproxies.org API key", "isSecret": true }
  },
  "required": ["proxyHost", "proxyPort", "proxyUser", "proxyPass"]
}
STEP 02

Create the proxy configuration in main.js

import { Actor } from 'apify';
import { CheerioCrawler } from 'crawlee';

await Actor.init();

const input = await Actor.getInput();
const { proxyHost, proxyPort, proxyUser, proxyPass } = input;

const proxyUrl = `http://${proxyUser}:${proxyPass}@${proxyHost}:${proxyPort}`;

// Single mobile slot — give it to Apify as a "pool of one"
const proxyConfiguration = await Actor.createProxyConfiguration({
  proxyUrls: [proxyUrl],
});
STEP 03

Wire it into CheerioCrawler

const crawler = new CheerioCrawler({
    proxyConfiguration,
    maxRequestsPerCrawl: 500,
    maxConcurrency: 5,
    useSessionPool: true,        // sticky sessions per request key
    persistCookiesPerSession: true,
    requestHandler: async ({ request, $, log, session }) => {
        const title = $('title').text();
        log.info(`[${session.id}] ${request.url} → ${title}`);
        await Actor.pushData({ url: request.url, title });
    },
    failedRequestHandler: async ({ request, log }) => {
        log.warning(`Giving up on ${request.url} after retries`);
    },
});

await crawler.run(['https://example.com', 'https://example.org']);
await Actor.exit();
STEP 04

PlaywrightCrawler variant

Identical wiring — just swap the crawler class:

import { PlaywrightCrawler } from 'crawlee';

const crawler = new PlaywrightCrawler({
    proxyConfiguration,
    launchContext: { launchOptions: { headless: true } },
    requestHandler: async ({ page, request }) => {
        await page.waitForLoadState('networkidle');
        const html = await page.content();
        await Actor.pushData({ url: request.url, htmlLength: html.length });
    },
});
STEP 05

Rotate the egress IP from inside the Actor

async function rotate() {
    const r = await fetch(
        'https://buy.mobileproxies.org/api/v1/proxies/us-mob-01/switch',
        { method: 'POST', headers: { Authorization: `Bearer ${input.apiKey}` } },
    );
    if (!r.ok) throw new Error('rotate failed: ' + r.status);
    await new Promise(r => setTimeout(r, 4000));  // wait for re-bind
}

// Call rotate() between batches, or inside failedRequestHandler.

Verify It Works

Add a one-off request handler that fetches the egress IP and logs the ASN:

requestHandler: async ({ session, log }) => {
    const proxyUrl = await proxyConfiguration.newUrl(session.id);
    const r = await fetch('https://api.ipify.org?format=json', {
        // crawlee handles the agent automatically; in raw fetch use undici ProxyAgent
    });
    log.info('egress IP: ' + (await r.text()));
}

Session Pool Patterns

Apify's SessionPool manages per-session cookies on top of the proxy. The mobile proxy is sticky (one egress IP per slot at a time), so the session pool maps to logical sessions: each session keeps its own cookie jar, headers and retry counter. When a session burns (4xx/5xx pattern), Apify retires it and creates a fresh one without you touching the proxy URL.

For truly different egress IPs, schedule rotations through our API or run multiple Apify Actors against multiple slots — each slot is its own egress identity.

Common Errors

"All proxy URLs failed, retiring session"

Crawlee marks the proxy bad after 3 consecutive non-2xx responses. Lower maxConcurrency and add a preNavigationHooks jitter — bursting on a single mobile IP looks bot-like.

"407 Proxy Authentication Required"

The proxy URL was passed without URL-encoding special characters in the password. Use encodeURIComponent(pass) when constructing the string.

Playwright leaks the real IP via WebRTC

Pass --disable-features=WebRtcHideLocalIpsWithMdns to launchOptions.args, or use the playwright-extra stealth plugin.

Related Guides

Drop Mobile IPs Into Your Apify Actor

$5 trial. proxyUrls just works. Real carrier ASNs, rotation API, sticky sessions.