Proxy-Cheap
Proxy-Cheap
Integrations
Proxy integration with Guzzle: a complete PHP setup guide

Guzzle Proxy Integration

Route Guzzle requests through Proxy-Cheap proxies to keep success rates stable across large PHP crawls and API jobs. Set a proxy once on the client or per request for rotation, and collect geo-specific data from 180+ countries on pay-as-you-go billing.
Get Proxies for Proxy integration with Guzzle: a complete PHP setup guide
Proxy integration with Guzzle: a complete PHP setup guide
What is Guzzle?
Guzzle is a free, open-source PHP HTTP client for calling APIs and fetching web pages, with support for async and concurrent requests. It has a built-in proxy option, so you can route requests through Proxy-Cheap IPs on the client or per request.

Key takeaways:

  • Guzzle is a PSR-7 PHP HTTP client. Proxies attach through the proxy request option, either as a single URL string or a per-protocol array.
  • Put credentials in the proxy URL as http://user:pass@host:port, and wrap them with rawurlencode() so special characters do not break the string.
  • Set proxy once on the client for a fixed IP, or per request to rotate. Concurrent jobs run through Pool or async promises.
  • Match the product to the workload: rotating residential for large geo-specific crawls, static residential or ISP for session-bound work, datacenter for high-volume public endpoints.

What is Guzzle?

Guzzle is a PHP HTTP client. It sends HTTP requests and talks to REST APIs and web services through a clean, object-oriented interface. It builds on the PSR-7 (HTTP messages), PSR-18 (HTTP client), and PSR-17 (factories) standards, so requests and responses are interoperable objects you can pass around any modern PHP or Laravel application.

Under the hood, Guzzle uses PHP's cURL extension when it is available and falls back to PHP stream wrappers when it is not. It supports synchronous and asynchronous requests, streaming uploads and downloads, a middleware handler stack, and connection pooling. The current series is Guzzle 7, version 7.11 at the time of writing, installed through Composer as guzzlehttp/guzzle.

Two facts shape everything in this guide. First, Guzzle reads a proxy request option that maps directly onto cURL's proxy handling, so every proxy capability cURL has is available to you. Second, options can be set once on the client or per request, which is exactly what makes proxy rotation simple.

What is Guzzle used for?

Guzzle is the default choice whenever PHP code needs to make an outbound HTTP call. The most common jobs:

  • Consuming third-party APIs. REST and GraphQL integrations in PHP and Laravel apps, with JSON request and response handling built in.
  • Web scraping and public-data collection. Guzzle fetches raw HTML or JSON, which you then parse with a DOM crawler or a JSON decoder.
  • Server-to-server calls. Internal requests between microservices, with timeouts and retries you control.
  • Integration and contract testing. Hit live or mocked endpoints, then assert on the response.
  • Streaming large files. Download or upload multi-gigabyte payloads without loading the whole body into memory.
  • Scheduled polling jobs. Cron tasks that pull feeds, prices, or search results across different markets.

Several of these scale to thousands of requests against the same set of hosts. That is the point where the source IP starts to matter, and where a proxy becomes part of the setup rather than an afterthought.

Key features of Guzzle

The capabilities that matter once you move past a single request:

  • PSR-7 requests and responses and a PSR-18 client interface, so Guzzle drops into any standards-based codebase.
  • Synchronous and asynchronous requests through promises, so slow calls do not stall the rest of your code.
  • Concurrent requests via Pool and GuzzleHttp\Promise\Utils, with a configurable limit on how many run at once.
  • A middleware handler stack for retries, logging, caching, and custom request and response logic.
  • First-class proxy support through the proxy option, as a single value or split by protocol.
  • Streaming bodies, so large payloads never load fully into memory.
  • Pluggable handlers (cURL, stream wrapper, and a mock handler for tests).

Is Guzzle free to use?

Yes. Guzzle is open source under the MIT license, free for both commercial and personal projects. There is no API key, no rate limit, and no usage cap inside the library. Its dependencies, including guzzlehttp/psr7 and guzzlehttp/promises, are MIT licensed too.

Your only costs are the infrastructure that runs your PHP and, once you scrape or call APIs at volume, proxies. The rest of this guide assumes you are running Guzzle on your own server and want to route its requests through a proxy network.

How to install Guzzle

Guzzle installs with Composer. It needs PHP 7.2.5 or newer, and PHP 8.1 or newer is recommended for current releases. Install the cURL extension as well: Guzzle works without it, but cURL is required for true concurrency and for SOCKS5 proxies.

composer require guzzlehttp/guzzle

Confirm the version you actually pulled before copying code from any tutorial:

composer show guzzlehttp/guzzle

This guide was written and tested against guzzlehttp/guzzle 7.11.0 on PHP 8.3 with libcurl 8.20. Once Composer finishes, include the autoloader and you are ready to send requests.

Quickstart: your first Guzzle request

The minimal working example creates a client, sends a GET request, and reads the response. Options passed to the constructor become defaults for every request the client sends.

<?php
require 'vendor/autoload.php';
 
use GuzzleHttp\Client;
 
// Constructor options become the defaults for every request this client sends.
$client = new Client([
    'base_uri' => 'https://api.ipify.org',
    'timeout'  => 10,
]);
 
$response = $client->request('GET', '/?format=json');
 
echo $response->getStatusCode();               // 200
echo $response->getHeaderLine('Content-Type'); // application/json
echo (string) $response->getBody();            // {"ip":"198.51.100.23"}

Run it and you get a 200 status, an application/json content type, and a small JSON body with your server's current outbound IP (shown here as an illustrative address).

Diagram contrasting Guzzle client constructor options that apply to every request with per-request options that override them, showing where the proxy setting can live.

Options set on the client are defaults for every request. Per-request options override them. The proxy setting can live in either place, which is the key to rotation.

The client-versus-request split is the single most useful idea for proxy work. Put the proxy option on the client and every request uses the same IP. Put it on the individual request and you can hand each call a different proxy. You will use both patterns below.

What does a Guzzle response contain?

A request returns a PSR-7 ResponseInterface object. You read it through a small, predictable set of methods.

MethodReturnsUse it for
getStatusCode()Integer such as 200Branching on success or failure
getReasonPhrase()String such as OKLogging and diagnostics
getHeaderLine('Name')Comma-joined header stringReading content type or rate-limit headers
getHeaders()Array of all headersInspecting the full header set
getBody()A PSR-7 stream objectStreaming large bodies in chunks
(string) getBody()The full body as a stringParsing JSON or HTML

The body is a stream (GuzzleHttp\Psr7\Stream), not a plain string. Cast it with (string) for small payloads, or read it in chunks with $body->read(8192) for large ones. For JSON APIs, decode it directly:

$response = $client->request('GET', 'https://api.ipify.org?format=json');
$data = json_decode((string) $response->getBody(), true);
echo $data['ip'];

One default to know early: Guzzle treats 4xx and 5xx responses as errors and throws an exception. Keep that behavior for APIs where a bad status is a real failure, or set 'http_errors' => false when you want to inspect the status code yourself. This matters for proxy work, because a proxy authentication failure surfaces as an exception you need to catch.

Why you need a proxy with Guzzle

A single server IP is fine for light API traffic. It stops being fine the moment you fetch the same host thousands of times, because many sites rate-limit repeated requests from one address. Routing through a proxy network spreads those requests across a pool of IPs, which is why production PHP scraping runs behind one. For criteria on picking a network, the guide to the best proxies for web scraping walks through the trade-offs.

Three concrete reasons to put a proxy in front of Guzzle at scale:

  1. Reliable connectivity and stable session quality. Distributing requests across many IPs keeps response success rates high across long jobs, where a single IP would hit rate limits.
  2. Accurate geo-specific data. Prices, catalogs, and search results vary by country and city. Routing through an IP in the target market is the only way to capture what a user there actually sees, which is what localization testing, ad verification, and market research depend on.
  3. Compliance with site terms and regional requirements. Public-web data collection at scale should respect rate limits and regional rules. Country and city targeting lets you stay inside those boundaries while still gathering the data you need.

Card layout mapping four Proxy-Cheap product lines to Guzzle workloads: rotating residential, static residential ISP, ISP, and datacenter proxies.

Each Proxy-Cheap product line fits a different shape of Guzzle workload. Pick by the target site, not by budget alone.

Proxy-Cheap offers four product lines that map cleanly onto typical Guzzle jobs:

  • Rotating Residential proxies for large geo-specific crawls, dynamic pricing pages, and any job that benefits from a fresh IP per request across a wide pool.
  • Static Residential (ISP) proxies for session-bound work: account dashboards, logged-in pages, and anything that needs the same trusted IP held for hours.
  • ISP proxies for long-lived sessions with flexible authentication, available in static and rotating options over HTTP and SOCKS5.
  • Datacenter proxies for high-volume requests to public endpoints, documentation, and catalogs, where raw throughput and predictable per-IP pricing matter most.

How to set up a proxy with Guzzle

Request flow diagram showing a Guzzle client sending a request through the Proxy-Cheap gateway to a target site and receiving the response back as a PSR-7 object.

A Guzzle request routes through the Proxy-Cheap gateway, reaches the target through an assigned IP, and returns a standard PSR-7 response.

Before the code, a note on endpoint format, because it differs by product:

  • Rotating residential uses a single shared hub: thehub.proxy-cheap.com:8080, with the country chosen in the dashboard credential generator, so there is no separate US or EU gateway. Each request through the hub gets a new IP automatically.
  • Static residential, ISP, and datacenter assign a unique IP and port to each proxy you order. You copy the exact host and port from the dashboard.

Guzzle's proxy option takes a single URL string. The cleanest pattern is to compose that string from named variables so credentials never sit hardcoded inline:

<?php
require 'vendor/autoload.php';
 
$proxyUser = '<your-proxycheap-username>';
$proxyPass = '<your-proxycheap-password>';
$proxyHost = 'thehub.proxy-cheap.com';
$proxyPort = '8080';
 
// rawurlencode keeps special characters in credentials from breaking the URL.
$proxy = sprintf('http://%s:%s@%s:%s',
    rawurlencode($proxyUser), rawurlencode($proxyPass), $proxyHost, $proxyPort);
 
$client = new GuzzleHttp\Client(['timeout' => 20]);
 
$response = $client->request('GET', 'https://httpbin.org/ip', [
    'proxy' => $proxy,
]);
 
echo (string) $response->getBody(); // echoes the exit IP assigned to the request

To send every request from a client through the same proxy, move the option into the constructor:

$client = new GuzzleHttp\Client([
    'proxy'   => $proxy, // applies to every request this client sends
    'timeout' => 20,
]);

Guzzle also accepts a per-protocol array. This routes HTTP and HTTPS through different proxies and lets you name hosts that should skip the proxy and connect directly:

$response = $client->request('GET', 'https://httpbin.org/ip', [
    'proxy' => [
        'http'  => $proxy,                     // for http:// targets
        'https' => $proxy,                     // for https:// targets
        'no'    => ['localhost', '127.0.0.1'], // connect directly, no proxy
    ],
]);

Three practical notes from real use:

  • Credentials go in the URL userinfo. Guzzle has no separate username and password fields, so the documented form is http://user:pass@host:port. Always pass the parts through rawurlencode().
  • Use the http:// scheme for the rotating gateway on port 8080. Proxy-Cheap supports HTTP and SOCKS5; for static products, copy the exact host and port from the Proxy-Cheap dashboard.
  • IP whitelist authentication is an option on static residential, ISP, and datacenter products. Authorize your server's IP in the dashboard and omit credentials entirely, which is handy for headless servers and CI runners. For a full walkthrough, see the guide to using residential proxies.

Rotating proxies with Guzzle

You get rotation in two ways, depending on the product line:

  • Rotating residential rotates at the hub. Every request through thehub.proxy-cheap.com:8080 gets a new exit IP automatically, or a sticky session of around 30 minutes when you need the same IP for a few related calls. You only need one entry in your config. The trade-offs against fixed IPs are covered in static vs rotating proxies.
  • Static residential, ISP, or datacenter give you fixed IPs. To rotate, keep a list and cycle through it in code.

For the second case, a round-robin over a proxy list is all you need. Set the proxy per request so each call can use a different IP:

$proxies = [
    'http://<user>:<pass>@thehub.proxy-cheap.com:8080',
    'http://<user>:<pass>@thehub.proxy-cheap.com:8080',
    // add static IPs and ports from your dashboard here
];
 
$client = new GuzzleHttp\Client(['timeout' => 20]);
 
foreach ($urls as $i => $url) {
    $proxy = $proxies[$i % count($proxies)]; // round-robin selection
    $response = $client->request('GET', $url, ['proxy' => $proxy]);
    echo $url . ' -> ' . $response->getStatusCode() . PHP_EOL;
}

Swap $proxies[$i % count($proxies)] for $proxies[array_rand($proxies)] if you prefer random selection over strict round-robin. The same per-request pattern applies in any language; the Python IP rotation guide shows the equivalent with Requests and AIOHTTP.

When to use which: reach for rotating residential when you crawl a high volume of geo-specific pages and want a fresh IP per request. Reach for static residential or ISP when a workflow needs the same IP held across a multi-step session, such as a logged-in dashboard.

Advanced: concurrent proxied requests with Guzzle

Fetching pages one at a time wastes most of your wall-clock time waiting on the network. Guzzle's async API and Pool send many requests at once, each through a proxy. Concurrency uses the cURL multi handler, so the cURL extension is required here.

Pool is the right tool for a large, fixed list of URLs sharing one proxy. It caps how many requests run at once and calls a handler as each one settles:

use GuzzleHttp\Client;
use GuzzleHttp\Pool;
use GuzzleHttp\Psr7\Request;
 
$client = new Client(['timeout' => 20]);
$proxy  = 'http://<user>:<pass>@thehub.proxy-cheap.com:8080';
 
$requests = function ($urls) {
    foreach ($urls as $url) {
        yield new Request('GET', $url);
    }
};
 
$pool = new Pool($client, $requests($targetUrls), [
    'concurrency' => 10,                   // requests in flight at once
    'options'     => ['proxy' => $proxy],  // applied to every request in the pool
    'fulfilled'   => function ($response, $i) {
        echo "[$i] " . $response->getStatusCode() . PHP_EOL;
    },
    'rejected'    => function ($reason, $i) {
        echo "[$i] failed: " . $reason->getMessage() . PHP_EOL;
    },
]);
 
$pool->promise()->wait();

When you want concurrency and rotation together, build the promises yourself with getAsync, give each its own proxy, then settle them as a group:

use GuzzleHttp\Client;
use GuzzleHttp\Promise\Utils;
 
$client  = new Client(['timeout' => 15]);
$proxies = [
    'http://<user>:<pass>@thehub.proxy-cheap.com:8080',
    'http://<user>:<pass>@thehub.proxy-cheap.com:8080',
];
 
$promises = [];
foreach ($targetUrls as $i => $url) {
    $proxy = $proxies[$i % count($proxies)];
    $promises[$i] = $client->getAsync($url, ['proxy' => $proxy]);
}
 
$results = Utils::settle($promises)->wait(); // resolves every promise, errors included
 
foreach ($results as $i => $result) {
    echo "[$i] " . $result['state'] . PHP_EOL; // fulfilled or rejected
}

Utils::settle waits for all promises and never throws, so one failed request does not sink the batch. Each entry reports fulfilled with a value or rejected with a reason, which is the right shape for a resilient crawl.

Common errors and how to fix them

Most Guzzle proxy failures fall into a few buckets. Each has a clear fix.

1. cURL error 7: CONNECT tunnel failed, response 407. The proxy demanded authentication and your credentials were missing or wrong. This is the most common proxy error, and it appears on HTTPS targets because the request tunnels through the proxy with CONNECT. Add credentials to the proxy URL as http://user:pass@host:port and confirm them in the dashboard. On some libcurl versions the same problem shows as cURL error 56: Received HTTP code 407 from proxy after CONNECT.

2. cURL error 7: Failed to connect to ... port. The host or port is wrong, or you pointed an HTTP client at a SOCKS-only port. For rotating residential, use thehub.proxy-cheap.com:8080 with the http:// scheme. For static products, copy the exact host and port from the dashboard.

3. cURL error 60: SSL certificate problem: unable to get local issuer certificate. Your PHP install has no CA bundle configured. Point curl.cainfo in php.ini at a current cacert.pem. Avoid disabling verification in production; turning off TLS checks defeats the purpose of HTTPS.

4. cURL error 28: Connection timed out. The proxy or target did not answer in time. Raise the timeout and connect_timeout options, and retry transient failures rather than failing the whole job (see the retry pattern below).

5. A bad password silently breaks the proxy URL. Special characters such as @, :, and # in a password split the URL in the wrong place. Always wrap credentials in rawurlencode().

6. Catch the right exceptions. Transport failures (a refused proxy, a tunnel error, a timeout) throw GuzzleHttp\Exception\ConnectException. HTTP responses with a 4xx or 5xx status throw GuzzleHttp\Exception\RequestException, which carries the response. Handle them separately, and retry only the transient transport errors:

use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ConnectException;
use Psr\Http\Message\RequestInterface;
 
$stack = HandlerStack::create();
$stack->push(Middleware::retry(
    function ($retries, RequestInterface $req, $res = null, $err = null) {
        return $retries < 2 && $err instanceof ConnectException; // retry transient connects
    },
    function ($retries) { return 1000 * $retries; } // back off 1s, then 2s
));
 
$client = new Client(['handler' => $stack, 'timeout' => 20]);

This handler stack retries a refused or timed-out proxy connection up to two times before giving up, which smooths over the occasional transient failure on a long job.

Is Guzzle good? Pros and cons

For outbound HTTP in PHP, Guzzle is the standard for good reason. It is MIT licensed, the PSR-7 design is clean, the async and concurrency support is genuinely useful, and proxy handling is a first-class option rather than a bolt-on. A few honest trade-offs are worth weighing.

Pros

  • MIT licensed, no usage caps, used across the PHP ecosystem.
  • PSR-7, PSR-17, and PSR-18 compliant, so it drops into standards-based code.
  • Synchronous, asynchronous, and concurrent requests in one library.
  • Middleware stack for retries, logging, and custom logic.
  • Proxy support maps onto every cURL proxy feature, including SOCKS5.

Cons

  • Concurrency and SOCKS5 need the cURL extension; the stream fallback is limited.
  • Proxy credentials live in the URL, so you must remember to rawurlencode() them.
  • It is an HTTP client, not a full crawler: you add your own parsing, scheduling, and proxy rotation logic.

For most PHP scraping and API projects, the practical setup is Guzzle for the requests, a small rotation helper for the IPs, and a value-tier proxy provider on pay-as-you-go billing. Proxy-Cheap fits that last piece with residential proxies across 180+ countries, HTTP and SOCKS5 support, and no monthly commitment.

FAQs

Yes, through the cURL handler. Pass a socks5:// or socks5h:// URL to the proxy option, where socks5h resolves DNS at the proxy. The cURL extension must be installed, because the stream-wrapper fallback does not handle SOCKS. Use the SOCKS host and port shown in your Proxy-Cheap dashboard rather than the HTTP gateway port.

For basic HTTP and HTTPS proxies, no: the stream wrapper can handle them. For SOCKS5 proxies and for concurrent requests, yes. The cURL extension is recommended for any production setup because it is faster and supports the full proxy feature set.

Yes. Pass proxy in the Client constructor and it becomes the default for every request that client sends. Override it per request when you need a different IP for a specific call, which is the basis for rotation.

Yes. Guzzle honors HTTP_PROXY, HTTPS_PROXY, and NO_PROXY when no explicit proxy option is set. For safety, HTTP_PROXY is only read when the script runs from the command line, not in a web request.

Use the array form of the proxy option and list the hosts under the no key, for example 'no' => ['localhost', '.internal.example.com']. Requests to those hosts connect directly instead of going through the proxy.

Wrap the username and password in rawurlencode() before building the proxy URL. Characters such as @, :, #, and / otherwise split the URL in the wrong place and cause authentication failures.

Guzzle adds a thin layer over cURL, so single requests are effectively the same speed. For many requests it is often faster in practice, because its Pool and async promises make concurrency easy to write correctly, whereas hand-rolled curl_multi code is harder to get right.

Guzzle 7 requires PHP 7.2.5 or newer. Recent 7.x releases are built and tested against PHP 8.1 and newer, so a modern PHP 8 runtime is the safe choice for new projects.

Yes. Guzzle fetches the HTML or JSON, and you pair it with a parser such as Symfony DomCrawler for markup or json_decode for APIs. For anything beyond light volume, route requests through a proxy and add rotation, as shown above.