← Back to Blog

Setting Up Proxies in PHP with cURL and Guzzle HTTP Client: Complete Guide with Code Examples

A complete guide to setting up a proxy in PHP using cURL and Guzzle HTTP Client β€” with code examples, IP rotation, and tips for choosing the type of proxy for scraping and automation.

πŸ“…August 12, 2026
```html

If you are writing a parser, automating data collection, or bypassing IP restrictions, using proxies in PHP is essential. In this guide, we'll explore two main tools: the built-in cURL and the popular library Guzzle HTTP Client β€” with ready-to-use code examples for your project.

Why use proxies in PHP projects

PHP remains one of the most popular languages for server-side automation, and tasks requiring proxies are common. Here are the main scenarios where proxies are indispensable:

  • Web scraping and marketplace scraping β€” Wildberries, Ozon, Avito, AliExpress block IPs after several dozen requests. Proxies allow distributing the load across different addresses and avoid receiving 403/429 errors.
  • Data collection from geo-dependent resources β€” prices, search engine results, and content can vary by country. Proxies with the required geolocation solve this issue.
  • Bypassing rate limiting β€” many APIs limit the number of requests from a single IP. Proxy rotation allows bypassing these limits.
  • Testing geo-dependent content β€” check how a website appears to users from different countries without leaving the office.
  • Anonymity during automation β€” hide the real IP of the server when making bulk requests to external resources.
  • Competitor monitoring β€” regularly collect prices, assortments, and promotions from competitors' websites without the risk of getting banned.

In PHP, two tools are most commonly used for HTTP requests: the built-in cURL extension and the Guzzle HTTP Client library. Both support HTTP, HTTPS, SOCKS4, and SOCKS5 proxies β€” we will examine each in detail.

Which type of proxy to choose for PHP

Before writing code, it's important to understand which type of proxy is suitable for your task. Different types have different characteristics in terms of speed, reliability, and level of anonymity.

Proxy Type Speed Anonymity Best for
Datacenter Proxies Very High Medium Scraping without strict anti-bot systems, API requests
Residential Proxies Medium High Scraping protected sites, marketplaces, geo data
Mobile Proxies Medium Maximum Websites with strict anti-bot protection, social networks

For most data scraping tasks from marketplaces like Wildberries or Ozon, residential proxies are the optimal choice β€” their IPs belong to real home users, making requests virtually indistinguishable from regular browser traffic. Datacenter proxies are suitable where speed is more important than anonymity, and protection is weak.

All three types of proxies support HTTP/HTTPS and SOCKS5 protocols, so from a coding perspective, the setup is the same. The only difference is in the connection string.

Proxies in cURL: basic setup

The cURL extension is available in PHP out of the box and is the standard way to perform HTTP requests on the server. Two main options are used to connect to a proxy: CURLOPT_PROXY and CURLOPT_PROXYTYPE.

HTTP Proxy without Authentication

<?php

$ch = curl_init();

curl_setopt_array($ch, [
    CURLOPT_URL            => 'https://httpbin.org/ip',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_FOLLOWLOCATION => true,
    CURLOPT_TIMEOUT        => 30,

    // Specify the proxy address
    CURLOPT_PROXY          => '185.199.100.1:8080',

    // Proxy type: HTTP (default)
    CURLOPT_PROXYTYPE      => CURLPROXY_HTTP,
]);

$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo "HTTP Code: $httpCode\n";
echo $response;

Here, CURLOPT_PROXY accepts a string in the format host:port. For HTTPS traffic through an HTTP proxy, cURL automatically uses the CONNECT method β€” tunneling, so the request content remains encrypted.

Useful cURL Options for Working with Proxies

Option Description
CURLOPT_PROXY Proxy server address (host:port)
CURLOPT_PROXYTYPE Type: CURLPROXY_HTTP, CURLPROXY_SOCKS4, CURLPROXY_SOCKS5
CURLOPT_PROXYUSERPWD Login and password in the format user:password
CURLOPT_HTTPPROXYTUNNEL Enable tunneling through HTTP CONNECT
CURLOPT_SSL_VERIFYPEER SSL certificate verification (false β€” disable)
CURLOPT_TIMEOUT Request timeout in seconds
CURLOPT_CONNECTTIMEOUT Timeout for connecting to the proxy

Authentication and SOCKS5 in cURL

Most commercial proxies require authentication with a username and password. Also, SOCKS5 is the preferred protocol when full anonymity is needed, as it does not add headers like X-Forwarded-For, which can reveal the use of a proxy.

HTTP Proxy with Username and Password

<?php

$proxyHost = '185.199.100.1';
$proxyPort = '8080';
$proxyUser = 'your_login';
$proxyPass = 'your_password';

$ch = curl_init();

curl_setopt_array($ch, [
    CURLOPT_URL            => 'https://httpbin.org/ip',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT        => 30,
    CURLOPT_CONNECTTIMEOUT => 10,

    // Proxy with authentication
    CURLOPT_PROXY          => "$proxyHost:$proxyPort",
    CURLOPT_PROXYTYPE      => CURLPROXY_HTTP,
    CURLOPT_PROXYUSERPWD   => "$proxyUser:$proxyPass",

    // Tunneling for HTTPS
    CURLOPT_HTTPPROXYTUNNEL => true,

    // Browser headers for masking
    CURLOPT_USERAGENT      => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
    CURLOPT_HTTPHEADER     => [
        'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
        'Accept-Language: ru-RU,ru;q=0.9,en;q=0.8',
    ],
]);

$response = curl_exec($ch);

if (curl_errno($ch)) {
    echo 'cURL Error: ' . curl_error($ch);
} else {
    echo $response;
}

curl_close($ch);

SOCKS5 Proxy in cURL

<?php

$ch = curl_init();

curl_setopt_array($ch, [
    CURLOPT_URL            => 'https://httpbin.org/ip',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT        => 30,

    CURLOPT_PROXY          => '185.199.100.1:1080',

    // SOCKS5 with DNS resolution through the proxy (recommended!)
    CURLOPT_PROXYTYPE      => CURLPROXY_SOCKS5_HOSTNAME,

    // Authentication (if required)
    CURLOPT_PROXYUSERPWD   => 'login:password',
]);

$response = curl_exec($ch);
curl_close($ch);

echo $response;

πŸ’‘ Important: SOCKS5 vs SOCKS5_HOSTNAME

Use CURLPROXY_SOCKS5_HOSTNAME instead of CURLPROXY_SOCKS5. The difference is that with SOCKS5_HOSTNAME, DNS requests also go through the proxy, preventing DNS leaks and enhancing anonymity. With regular SOCKS5, DNS is resolved locally β€” this can expose your real IP.

Proxy Rotation in cURL

A single proxy will quickly get blocked with mass requests. The right strategy is to have a pool of proxies and alternate them. Here’s a simple but effective implementation:

<?php

class ProxyRotator
{
    private array $proxies;
    private int $currentIndex = 0;

    public function __construct(array $proxies)
    {
        $this->proxies = $proxies;
        shuffle($this->proxies); // Shuffle for random order
    }

    /**
     * Get the next proxy from the pool
     */
    public function getNext(): string
    {
        $proxy = $this->proxies[$this->currentIndex];
        $this->currentIndex = ($this->currentIndex + 1) % count($this->proxies);
        return $proxy;
    }

    /**
     * Get a random proxy
     */
    public function getRandom(): string
    {
        return $this->proxies[array_rand($this->proxies)];
    }
}

// List of proxies in the format login:password@host:port
$proxyList = [
    'user1:[email protected]:8080',
    'user2:[email protected]:8080',
    'user3:[email protected]:8080',
    'user4:[email protected]:8080',
];

$rotator = new ProxyRotator($proxyList);

/**
 * Function to make a request with automatic proxy selection
 */
function fetchWithProxy(string $url, ProxyRotator $rotator): ?string
{
    $proxyStr = $rotator->getNext();

    // Parse the proxy string
    preg_match('/^(.+):(.+)@(.+):(\d+)$/', $proxyStr, $m);
    [, $user, $pass, $host, $port] = $m;

    $ch = curl_init();
    curl_setopt_array($ch, [
        CURLOPT_URL            => $url,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT        => 20,
        CURLOPT_CONNECTTIMEOUT => 8,
        CURLOPT_PROXY          => "$host:$port",
        CURLOPT_PROXYTYPE      => CURLPROXY_HTTP,
        CURLOPT_PROXYUSERPWD   => "$user:$pass",
        CURLOPT_USERAGENT      => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)',
        CURLOPT_SSL_VERIFYPEER => false,
    ]);

    $response = curl_exec($ch);
    $error    = curl_error($ch);
    curl_close($ch);

    if ($error) {
        error_log("Proxy $host:$port is unavailable: $error");
        return null;
    }

    return $response;
}

// Example: scrape 10 pages with proxy rotation
$urls = [
    'https://example.com/page/1',
    'https://example.com/page/2',
    'https://example.com/page/3',
    // ...
];

foreach ($urls as $url) {
    $html = fetchWithProxy($url, $rotator);
    if ($html) {
        echo "Received: " . strlen($html) . " bytes\n";
    }
    usleep(500000); // Pause 0.5 seconds between requests
}

Note the usleep(500000) β€” the pause between requests is critically important. Even with proxy rotation, too frequent requests can lead to blocking based on behavioral patterns. The recommended interval is between 500 ms to 2 seconds depending on the site.

Proxies in Guzzle HTTP Client: basic setup

Guzzle is a powerful PHP library for HTTP requests that is used in most modern PHP frameworks (Laravel, Symfony). It provides a more convenient and readable API compared to raw cURL, supports asynchronous requests, middleware, and easy error handling.

Installation via Composer

composer require guzzlehttp/guzzle

HTTP Proxy without Authentication

<?php

require 'vendor/autoload.php';

use GuzzleHttp\Client;

$client = new Client([
    // Base URL (optional)
    'base_uri' => 'https://httpbin.org',

    // Default settings for all requests
    'timeout'  => 30,
    'connect_timeout' => 10,

    // Proxy for all client requests
    'proxy' => 'http://185.199.100.1:8080',
]);

$response = $client->get('/ip');

echo $response->getStatusCode() . "\n";  // 200
echo $response->getBody() . "\n";        // {"origin": "185.199.100.1"}

In Guzzle, the proxy is specified via the proxy option in URL format. This is more intuitive than in cURL. Proxies can be set at the client level (for all requests) or for each request individually.

HTTP Proxy with Authentication

<?php

use GuzzleHttp\Client;

$login    = 'your_login';
$password = 'your_password';
$host     = '185.199.100.1';
$port     = '8080';

$client = new Client([
    'timeout' => 30,
]);

// Proxy with authentication is passed in URL format
$response = $client->get('https://httpbin.org/ip', [
    'proxy' => "http://$login:$password@$host:$port",
    'headers' => [
        'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
        'Accept'     => 'text/html,application/xhtml+xml,application/xml;q=0.9',
    ],
]);

echo $response->getBody();

The proxy URL format in Guzzle is: scheme://user:password@host:port. For HTTP proxies, use http://, for SOCKS5 β€” socks5://.

Advanced usage with Guzzle: timeouts, headers, SOCKS5

Let's consider more complex scenarios: different proxies for HTTP and HTTPS, SOCKS5, disabling SSL verification, and setting headers to mimic a browser.

Different Proxies for HTTP and HTTPS

<?php

use GuzzleHttp\Client;

$client = new Client([
    'timeout' => 30,
    'proxy'   => [
        // Proxy for HTTP requests
        'http'  => 'http://login:[email protected]:8080',
        // Proxy for HTTPS requests
        'https' => 'http://login:[email protected]:8080',
        // Exceptions β€” these hosts go without a proxy
        'no'    => ['localhost', '127.0.0.1', '.internal.corp'],
    ],
]);

SOCKS5 in Guzzle

<?php

use GuzzleHttp\Client;

// For SOCKS5 in Guzzle, an additional package is needed
// composer require clue/socks-react (or use curl handler)

$client = new Client([
    'timeout' => 30,
    'proxy'   => 'socks5://login:[email protected]:1080',
    'curl'    => [
        CURLOPT_PROXYTYPE => CURLPROXY_SOCKS5_HOSTNAME,
    ],
]);

$response = $client->get('https://httpbin.org/ip');
echo $response->getBody();

Full Configuration with Browser Headers

<?php

use GuzzleHttp\Client;
use GuzzleHttp\RequestOptions;

$client = new Client([
    'timeout'         => 30,
    'connect_timeout' => 10,
    'verify'          => false, // Disable SSL verification (for debugging)
    'allow_redirects' => [
        'max'       => 5,
        'strict'    => false,
        'referer'   => true,
        'protocols' => ['http', 'https'],
    ],
    'headers' => [
        'User-Agent'      => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
        'Accept'          => 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
        'Accept-Language' => 'ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7',
        'Accept-Encoding' => 'gzip, deflate, br',
        'Cache-Control'   => 'no-cache',
        'Connection'      => 'keep-alive',
    ],
]);

$response = $client->get('https://example.com/catalog', [
    'proxy' => 'http://login:[email protected]:8080',
]);

$statusCode = $response->getStatusCode();
$body       = (string) $response->getBody();

echo "Status: $statusCode, Size: " . strlen($body) . " bytes\n";

⚠️ About verify => false

Disabling SSL verification ('verify' => false) is only acceptable during debugging or when working with internal services. In production code, always keep SSL verification enabled, otherwise, you are vulnerable to man-in-the-middle attacks.

Proxy Rotation in Guzzle

In Guzzle, rotation can be conveniently implemented through middleware β€” an intermediate layer that intercepts each request and adds a proxy to it. This is more elegant than passing the proxy manually in each call.

<?php

use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;
use Psr\Http\Message\RequestInterface;

/**
 * Middleware for automatic proxy rotation
 */
function proxyRotationMiddleware(array $proxies): callable
{
    $index = 0;
    $total = count($proxies);

    return Middleware::mapRequest(
        function (RequestInterface $request) use ($proxies, &$index, $total) {
            $proxy = $proxies[$index % $total];
            $index++;

            // Add the proxy to the request attributes
            // (pass through options in handler)
            return $request->withHeader('X-Selected-Proxy', $proxy);
        }
    );
}

// List of proxies
$proxies = [
    'http://user1:[email protected]:8080',
    'http://user2:[email protected]:8080',
    'http://user3:[email protected]:8080',
];

// Simple option: wrapper class around Guzzle with rotation
class GuzzleWithRotation
{
    private Client $client;
    private array $proxies;
    private int $index = 0;

    public function __construct(array $proxies, array $clientConfig = [])
    {
        $this->proxies = $proxies;
        $this->client  = new Client($clientConfig);
    }

    public function get(string $url, array $options = []): \GuzzleHttp\Psr7\Response
    {
        $options['proxy'] = $this->proxies[$this->index % count($this->proxies)];
        $this->index++;

        return $this->client->get($url, $options);
    }

    public function post(string $url, array $options = []): \GuzzleHttp\Psr7\Response
    {
        $options['proxy'] = $this->proxies[$this->index % count($this->proxies)];
        $this->index++;

        return $this->client->post($url, $options);
    }
}

// Usage
$guzzle = new GuzzleWithRotation($proxies, [
    'timeout' => 30,
    'headers' => [
        'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)',
    ],
]);

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

foreach ($urls as $url) {
    try {
        $response = $guzzle->get($url);
        echo "OK [{$response->getStatusCode()}]: $url\n";
        sleep(1);
    } catch (\Exception $e) {
        echo "Error: " . $e->getMessage() . "\n";
    }
}

Error Handling and Debugging

When working with proxies, errors are inevitable: the proxy may be unavailable, respond slowly, or the target site may return an error. It is important to handle all these situations properly.

Error Handling in cURL

<?php

function fetchWithErrorHandling(string $url, string $proxy): array
{
    $ch = curl_init();
    curl_setopt_array($ch, [
        CURLOPT_URL            => $url,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT        => 20,
        CURLOPT_CONNECTTIMEOUT => 8,
        CURLOPT_PROXY          => $proxy,
        CURLOPT_PROXYTYPE      => CURLPROXY_HTTP,
    ]);

    $body     = curl_exec($ch);
    $errno    = curl_errno($ch);
    $error    = curl_error($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $totalTime = curl_getinfo($ch, CURLINFO_TOTAL_TIME);
    curl_close($ch);

    // cURL error codes for proxies
    $proxyErrors = [
        CURLE_COULDNT_CONNECT    => 'Could not connect to proxy',
        CURLE_OPERATION_TIMEDOUT => 'Connection timeout to proxy',
        CURLE_RECV_ERROR         => 'Error receiving data',
        CURLE_SSL_CONNECT_ERROR  => 'SSL error through proxy',
    ];

    if ($errno) {
        $message = $proxyErrors[$errno] ?? "cURL error #$errno: $error";
        return ['success' => false, 'error' => $message, 'code' => $errno];
    }

    if ($httpCode >= 400) {
        return ['success' => false, 'error' => "HTTP $httpCode", 'code' => $httpCode];
    }

    return [
        'success' => true,
        'body'    => $body,
        'code'    => $httpCode,
        'time'    => round($totalTime, 3),
    ];
}

// Usage with retries
function fetchWithRetry(string $url, array $proxies, int $maxRetries = 3): ?string
{
    foreach ($proxies as $proxy) {
        for ($attempt = 1; $attempt <= $maxRetries; $attempt++) {
            $result = fetchWithErrorHandling($url, $proxy);

            if ($result['success']) {
                echo "Success through $proxy (attempt $attempt, {$result['time']}s)\n";
                return $result['body'];
            }

            echo "Error through $proxy: {$result['error']}\n";

            if ($attempt < $maxRetries) {
                sleep(2); // Pause before retrying
            }
        }
    }

    return null; // All proxies failed
}

Error Handling in Guzzle

<?php

use GuzzleHttp\Client;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Exception\ServerException;
use GuzzleHttp\Exception\ClientException;

$client = new Client(['timeout' => 30]);

$proxies = [
    'http://user1:[email protected]:8080',
    'http://user2:[email protected]:8080',
];

function fetchGuzzleWithFallback(Client $client, string $url, array $proxies): ?string
{
    foreach ($proxies as $proxy) {
        try {
            $response = $client->get($url, [
                'proxy'   => $proxy,
                'timeout' => 20,
            ]);

            return (string) $response->getBody();

        } catch (ConnectException $e) {
            // Proxy unavailable or connection timeout
            echo "Proxy unavailable ($proxy): " . $e->getMessage() . "\n";

        } catch (ClientException $e) {
            // HTTP 4xx β€” client error (403, 404, 429)
            $code = $e->getResponse()->getStatusCode();
            echo "HTTP $code for $url through $proxy\n";

            if ($code === 429) {
                echo "Rate limit β€” pausing for 5 seconds\n";
                sleep(5);
            }

        } catch (ServerException $e) {
            // HTTP 5xx β€” server error
            echo "Server error: " . $e->getResponse()->getStatusCode() . "\n";

        } catch (RequestException $e) {
            // Other request errors
            echo "Request error: " . $e->getMessage() . "\n";
        }
    }

    return null;
}

cURL vs Guzzle: which to choose

Both tools work great with proxies but have different strengths. Here’s a comparison based on key criteria:

Criterion cURL Guzzle
Installation βœ… Built into PHP ⚠️ Requires Composer
Code Readability ⚠️ Many options, verbose βœ… Clean, user-friendly API
Performance βœ… Slightly faster (no wrappers) βœ… Comparable, has async
Asynchronous Requests ⚠️ curl_multi (complex) βœ… Built-in support
Middleware / Hooks ❌ None βœ… HandlerStack, Middleware
SOCKS5 Support βœ… Native βœ… Via curl handler
Error Handling ⚠️ Error codes manually βœ… Exceptions with types
Integration in Laravel/Symfony ⚠️ Manually βœ… Native support
Suitable for Simple scripts, no dependencies Framework projects, complex logic

Recommendation: If you are writing a simple script or working on a hosting environment without Composer β€” use cURL. For projects in Laravel, Symfony, or any modern PHP applications β€” Guzzle will be significantly more convenient and provide more opportunities for scaling.

πŸš€ Quick checklist before launching

  • βœ… Check that the proxy works: curl -x http://login:pass@host:port https://httpbin.org/ip
  • βœ… Set timeouts: timeout and connect_timeout
  • βœ… Add a realistic User-Agent
  • βœ… Implement pauses between requests (minimum 500 ms)
  • βœ… Provide error handling and fallback to another proxy
  • βœ… For SOCKS5, use CURLPROXY_SOCKS5_HOSTNAME (not SOCKS5)
  • βœ… Do not disable SSL verification in production

Conclusion

Setting up proxies in PHP is not a difficult task if you know the right options and formats. For basic tasks, cURL with CURLOPT_PROXY and CURLOPT_PROXYUSERPWD is sufficient. For framework projects, Guzzle provides a more convenient API, typed exceptions, and middleware support for proxy rotation.

Key principles for reliable operation: use SOCKS5 with DNS resolution through the proxy (CURLPROXY_SOCKS5_HOSTNAME), always set timeouts, add pauses between requests, and implement proxy rotation for large tasks. Error handling with fallback to another proxy is an essential element of any production code.

Thank you for reading this guide! Happy coding!

```