PHP · setup guide

PHP proxy setup

A PHP proxy setup is two cURL options: CURLOPT_PROXY for the gateway and CURLOPT_PROXYUSERPWD for the credentials. Guzzle runs on the same cURL underneath and takes a proxy request option, so both routes behave alike and fail with the same cURL error numbers.
You will need

Residentialresi.proxymonkey.io:8000

ISP or datacenterIP:PORT

CredentialsUSER:PASS

Copy your own from the dashboard, which also lists the host and port for every order. Where it differs from this page, the dashboard is right.

Install

PHP 8.1 or newer with the curl extension loaded. Guzzle comes from Composer.

terminal
php -m | grep curl
composer require guzzlehttp/guzzle

Rotating residential

A new cURL handle starts with no open connections, so each pass through the loop opens a new tunnel through the residential gateway and prints a new address. HTTPS targets are tunnelled with CONNECT automatically; there is nothing extra to set.

rotate.php
<?php

$proxy = 'http://USER:[email protected]:8000';

for ($i = 0; $i < 3; $i++) {
    $ch = curl_init('https://httpbin.org/ip');
    curl_setopt_array($ch, [
        CURLOPT_PROXY => $proxy,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_CONNECTTIMEOUT => 10,
        CURLOPT_TIMEOUT => 30,
    ]);
    $body = curl_exec($ch);
    echo $body === false ? curl_error($ch) : json_decode($body)->origin, "\n";
}

A static datacenter or ISP IP

Guzzle takes the proxy as a request option, set once on the client. One client reuses its cURL handles and their connections, which is what you want on a static datacenter IP. For an ISP order, use the host and port from your dashboard.

static.php
<?php

require 'vendor/autoload.php';

use GuzzleHttp\Client;

$client = new Client([
    'proxy' => 'http://USER:PASS@IP:PORT',
    'connect_timeout' => 10,
    'timeout' => 30,
]);

foreach (['https://httpbin.org/ip', 'https://httpbin.org/headers'] as $url) {
    $response = $client->get($url);
    echo $response->getStatusCode(), ' ', substr((string) $response->getBody(), 0, 80), "\n";
}

Keeping one identity

A Guzzle client, like a reused cURL handle, keeps its connection open between requests, so on the rotating gateway consecutive requests can share a tunnel and an address. To rotate with Guzzle, pass CURLOPT_FORBID_REUSE => true in the curl request option. For a residential address that holds across new connections, such as through a login, use a sticky session; the session setting for your account is in the dashboard. Guzzle keeps cookies only if you ask it to, with 'cookies' => true.

Specific to PHP

Things worth knowing

PHP proxy authentication with CURLOPT_PROXYUSERPWD

Credentials inside the proxy URL break when the password contains @, : or #. CURLOPT_PROXYUSERPWD keeps them out of the URL, and in Guzzle the curl request option passes the same constant through. For a username that contains a colon, use CURLOPT_PROXYUSERNAME and CURLOPT_PROXYPASSWORD instead.

auth.php
curl_setopt($ch, CURLOPT_PROXY, 'http://resi.proxymonkey.io:8000');
curl_setopt($ch, CURLOPT_PROXYUSERPWD, 'USER:PASS');

$client = new GuzzleHttp\Client([
    'proxy' => 'http://resi.proxymonkey.io:8000',
    'curl' => [CURLOPT_PROXYUSERPWD => 'USER:PASS'],
]);

Read the bill off curl_getinfo

We meter request bytes plus response bytes, headers included, and cURL reports all four parts. Leave CURLOPT_ENCODING unset and the download size is the size on the wire. Our figure will be slightly higher, because TLS overhead on the tunnel is real traffic that cURL does not count.

bytes.php
$ch = curl_init('https://example.com/');
curl_setopt_array($ch, [CURLOPT_PROXY => $proxy, CURLOPT_RETURNTRANSFER => true]);
curl_exec($ch);
$info = curl_getinfo($ch);
echo $info['request_size'] + $info['size_upload'] + $info['header_size'] + $info['size_download'], "\n";

Guzzle proxy arrays need an https key

The proxy option also takes an array keyed by scheme, with no for hosts to skip. A URL whose scheme has no key goes out directly, from your own address, so set https as well as http.

client options
'proxy' => [
    'http' => 'http://USER:[email protected]:8000',
    'https' => 'http://USER:[email protected]:8000',
    'no' => ['localhost'],
],
When it breaks

Common errors and fixes

  • cURL error 56: CONNECT tunnel failed, response 407

    Why
    The gateway rejected the credentials on an HTTPS URL. Older libcurl builds word it Received HTTP code 407 from proxy after CONNECT.
    Fix
    Move the credentials into CURLOPT_PROXYUSERPWD, copy them fresh from the dashboard, and check nothing trailing came in from a .env value.
  • cURL error 28: Operation timed out after 30001 milliseconds

    Why
    No response within CURLOPT_TIMEOUT or Guzzle's timeout. Both default to no limit at all, so without one a stalled exit hangs the script.
    Fix
    Set a connect and a total timeout, catch the failure and retry. On the rotating gateway a retry on a new handle leaves from a new address.
  • cURL error 60: SSL certificate problem: unable to get local issuer certificate

    Why
    PHP cannot find a CA bundle, common on Windows installs, or the target's chain is incomplete. TLS runs through the tunnel to the target; the proxy is not part of it.
    Fix
    Download a CA bundle and point curl.cainfo in php.ini at it. CURLOPT_SSL_VERIFYPEER => false is for a quick test only.
  • cURL error 35 with wrong version number, right after connecting to the proxy

    Why
    The proxy URL starts with https://, so cURL tries to speak TLS to the gateway, which expects plain HTTP.
    Fix
    Use http:// for the proxy URL. The tunnel to an HTTPS site is still encrypted end to end.

A failed connection that moved no data is not billed. Retries you send are billed like any other request, and each shows as its own line in your usage log.

The community layer

PHP still misbehaving?

Paste the error and the few lines that set up the proxy into Discord, with the password taken out. Someone there has seen it before.

Join the Discord

4,200+monkeys in the Discord

  • Help from humans

    Post your error, get an answer. Usually in minutes, usually from someone who has hit the same wall.

  • A status bot that tells on us

    Pool health, incidents and maintenance posted automatically. Including the bad days.

  • Deals and free traffic

    Bonus GB drops, early access to new pools, and the occasional giveaway for a good bug report.

Join the Discord4,200+ monkeys, free to lurk