Before you start

  • PHP 8 with the curl extension enabled. Debian and Ubuntu package it separately, named after your PHP version, for example php8.3-curl.
  • Composer for the Guzzle example: composer require guzzlehttp/guzzle.
  • HOST, PORT, USERNAME and PASSWORD from the service page in the dashboard.

Your connection details

Sign in to the dashboard and open the service you bought. Its page shows the host, port, username and password for that service. There is no single ProxyPanda address to remember, so copy them from there each time. The code in these guides uses the placeholders below; replace each one with your own value.

HOST
The proxy address for this service, exactly as the service page shows it.
PORT
The port to connect to. Copy it with the host, since it can differ from one service to the next.
USERNAME
Your proxy login. It is separate from the email you use for the dashboard.
PASSWORD
Copy it in full. On residential, the options you choose in the dashboard are added to the end of the password, so a password typed from memory loses them.

You can skip the username and password by adding the IP address you connect from to the service’s allowlist. Then only HOST and PORT go into your code.

The examples use HTTP, which reaches https sites through an encrypted tunnel. SOCKS5 works too: residential ports accept both, and ISP and datacenter proxies switch protocol in the dashboard.

Steps

  1. Confirm the curl extension is loaded

    php -m prints every loaded extension. When curl is absent from that list, install the package for your PHP build or uncomment extension=curl in php.ini, then look again.

    terminal
    php -m | grep -i curl
  2. Point a cURL handle at the proxy

    CURLOPT_PROXY holds http://HOST:PORT and CURLOPT_PROXYUSERPWD holds USERNAME:PASSWORD. The two timeouts cap how long PHP waits to reach the proxy and how long the whole transfer may run. The script echoes the address api.ipify.org saw.

    check_ip.php
    <?php
    $ch = curl_init('https://api.ipify.org?format=json');
    curl_setopt_array($ch, [
        CURLOPT_PROXY => 'http://HOST:PORT',
        CURLOPT_PROXYUSERPWD => 'USERNAME:PASSWORD',
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_CONNECTTIMEOUT => 10,
        CURLOPT_TIMEOUT => 30,
    ]);
    
    $body = curl_exec($ch);
    if ($body === false) {
        echo 'cURL error ', curl_errno($ch), ': ', curl_error($ch), PHP_EOL;
    } else {
        echo $body, PHP_EOL;
    }
  3. Know when CURLOPT_HTTPPROXYTUNNEL matters

    For an https URL, cURL opens a CONNECT tunnel through the proxy by itself, the site’s TLS runs end to end, and this option changes nothing. Setting it to true forces a tunnel for plain http URLs too. Leave it out and those requests reach the proxy as ordinary HTTP, which suits most jobs.

  4. Make the same call with Guzzle

    Guzzle reads one proxy URL with the login inside it, while connect_timeout and timeout play the part of the two cURL timeouts. When the password contains @, : or /, run it through rawurlencode before building the URL.

    guzzle.php
    <?php
    require __DIR__ . '/vendor/autoload.php';
    
    use GuzzleHttp\Client;
    
    $client = new Client([
        'proxy' => 'http://USERNAME:PASSWORD@HOST:PORT',
        'connect_timeout' => 10,
        'timeout' => 30,
    ]);
    
    $response = $client->get('https://api.ipify.org?format=json');
    echo $response->getBody(), PHP_EOL;
  5. Take turns across your static IPs

    ISP and datacenter services rent you a set of fixed addresses. Save them in proxies.txt, one per line as HOST:PORT:USERNAME:PASSWORD, and the loop gives each request to the next line along. Replace the repeated ipify URL with the pages you fetch.

    rotate.php
    <?php
    $proxies = file('proxies.txt', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
    $urls = array_fill(0, 6, 'https://api.ipify.org');
    
    foreach ($urls as $i => $url) {
        [$host, $port, $user, $pass] = explode(':', trim($proxies[$i % count($proxies)]), 4);
    
        $ch = curl_init($url);
        curl_setopt_array($ch, [
            CURLOPT_PROXY => "$host:$port",
            CURLOPT_PROXYUSERPWD => "$user:$pass",
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_CONNECTTIMEOUT => 10,
            CURLOPT_TIMEOUT => 30,
        ]);
    
        $body = curl_exec($ch);
        echo $host, ' -> ', $body === false ? curl_error($ch) : $body, PHP_EOL;
    }

Rotating and sticky IPs

Every curl_init makes a new handle with a new connection. On residential with Randomize IP, running the first script twice prints two addresses.

Reusing one handle, or one Guzzle client, keeps its connection alive between calls, and requests sharing it leave from one address. That suits Sticky IP and static IPs; on Randomize IP, make a new handle when you want a new exit.

The rotation loop is meant for static lines, where each row of proxies.txt is a different fixed IP. Residential rotates on the proxy side, so a single row set to Randomize IP gives the same effect.

Common errors and fixes

Call to undefined function curl_init()

The PHP that ran the script has no curl extension loaded. Command-line PHP and your web server’s PHP can read different php.ini files; php --ini shows which one the command line uses.

cURL error 56: CONNECT tunnel failed, response 407

The proxy refused the login. Copy USERNAME and PASSWORD again. In Guzzle, encode special characters with rawurlencode, or move to plain cURL, where the password in CURLOPT_PROXYUSERPWD needs no encoding.

cURL error 7: Failed to connect

No proxy answered at that host and port. Hold both up against the service page, then confirm in the dashboard that the service has not run out.

cURL error 28: Operation timed out

The transfer ran past CURLOPT_TIMEOUT. Residential exits are home connections and can be slow, so allow more time and retry that single URL rather than the whole batch.

cURL error 60: SSL certificate problem: unable to get local issuer certificate

PHP cannot find a CA bundle, which happens most on Windows builds. Point curl.cainfo in php.ini at a current cacert.pem. Switching certificate checks off hides the problem and is not the fix.

Which line to pick

For PHP scrapers and price checks against sites that accept server traffic, datacenter IPs are the low-cost start, and the rotation loop spreads the work over each IP you rent. Move to residential once requests from server ranges get blocked, and choose ISP when one address has to stay signed in for weeks.

Stuck on a step?

Paste the command and the error into Discord, with your password taken out. People there have met most of these errors before.

Join the Discorddiscord.gg/proxypanda
Start with $5Ask in Discord