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:
timeoutandconnect_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!
```