JustProxies

Examples

PHP (Guzzle · cURL)

5-min readphp
Two ways to route PHP traffic through the proxy: Guzzle (recommended for new code) and the built-in cURL extension for legacy scripts, WordPress plugins, and Laravel contexts that don't want the extra dependency.

Guzzle

Pass a proxy key in the request options. The value is a standard proxy URL with embedded credentials.

guzzle-basic.phptext
use GuzzleHttp\Client;

$client = new Client();

$response = $client->get('https://api.ipify.org', [
    'proxy'   => 'http://USER:[email protected]:8080',
    'timeout' => 20,
]);

echo $response->getStatusCode(), PHP_EOL;
echo $response->getBody(),       PHP_EOL;
The proxy URL is always http:// — that is the connection scheme to the proxy, not to the target. Guzzle issues a CONNECT tunnel for HTTPS targets automatically.

Shared client instance

Set the proxy at construction time so every request through the same client reuses it. Guzzle keeps the connection pool warm between calls, which matters when you are fetching hundreds of URLs in sequence.

guzzle-client.phptext
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Exception\ConnectException;

$client = new Client([
    'proxy' => [
        'http'  => 'http://USER:[email protected]:8080',
        'https' => 'http://USER:[email protected]:8080',
    ],
    'timeout' => 20,
    'verify'  => true,
]);

$urls = [
    'https://example.com/page-1',
    'https://example.com/page-2',
];

foreach ($urls as $url) {
    try {
        $res = $client->get($url);
        echo $res->getStatusCode(), ' ', $url, PHP_EOL;
    } catch (RequestException $e) {
        echo 'ERR ', $url, ': ', $e->getMessage(), PHP_EOL;
    }
}

PHP cURL extension

For scripts that use curl_init directly, set CURLOPT_PROXY and CURLOPT_PROXYUSERPWD. Split the credentials from the host so cURL handles URL-encoding for you.

curl-ext.phptext
$ch = curl_init();

curl_setopt_array($ch, [
    CURLOPT_URL            => 'https://api.ipify.org',
    CURLOPT_PROXY          => 'gw.justproxies.online:8080',
    CURLOPT_PROXYUSERPWD   => 'USER:PASS',
    CURLOPT_PROXYTYPE      => CURLPROXY_HTTP,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT        => 20,
    CURLOPT_SSL_VERIFYPEER => true,
    CURLOPT_SSL_VERIFYHOST => 2,
]);

$body = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$err  = curl_errno($ch) ? curl_error($ch) : null;
curl_close($ch);

if ($err) { echo "cURL error: {$err}
"; exit(1); }
echo "{$code}
{$body}
";
Don't disable CURLOPT_SSL_VERIFYPEER. The proxy is a CONNECT tunnel — it never sees or touches TLS traffic. If you hit cert errors, the problem is on the target side.

Sticky sessions

Append -session-TOKEN to the username to keep the same exit IP across calls. Works identically with Guzzle and raw cURL.

sticky.phptext
$sessionToken = bin2hex(random_bytes(8));  // e.g. "a3f1b2c4d5e6f7a8"
$proxyUser    = "USER-session-{$sessionToken}";

$client = new GuzzleHttp\Client([
    'proxy'   => "http://{$proxyUser}:[email protected]:8080",
    'timeout' => 20,
]);

// All three calls exit through the same IP for the session lifetime.
$client->post('https://target.com/login', ['form_params' => ['u' => '...', 'p' => '...']]);
$client->get('https://target.com/dashboard');
$client->get('https://target.com/checkout');

Retry / backoff

Most failures are transient: upstream target hiccups, momentary connection resets. Retry with exponential backoff, skip terminal codes (407, 451).

retry.phptext
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Exception\ConnectException;

function fetch(GuzzleHttp\Client $client, string $url, int $maxAttempts = 4): string {
    $retryable = [429, 500, 502, 503, 504, 522];

    for ($attempt = 0; $attempt < $maxAttempts; $attempt++) {
        try {
            return (string) $client->get($url)->getBody();
        } catch (ConnectException) {
            // network-level failure — always retry
        } catch (RequestException $e) {
            $code = $e->getResponse()?->getStatusCode() ?? 0;
            if (!in_array($code, $retryable, true)) { throw $e; }
        }

        if ($attempt + 1 < $maxAttempts) {
            // exponential backoff with jitter, capped at 8 s
            $base = min(8000, 400 * (2 ** $attempt));
            usleep(($base + random_int(0, intdiv($base, 2))) * 1000);
        }
    }

    throw new \RuntimeException("Gave up after {$maxAttempts} attempts on {$url}");
}
On rotating products each retry pulls a different exit IP, so a transient block on one IP usually clears within the first retry.
Found a gap, or something wrong?
A real human reads support email.