15% off your first purchase with code WELCOME15, up to 25 GB.

See pricing

Integration · HTTP clients

How to use a proxy with PHP Guzzle and cURL

Guzzle uses a proxy through its proxy request option: a URL such as http://user:pass@host:port, set on the client or on a single request. Plain PHP cURL does the same with CURLOPT_PROXY and CURLOPT_PROXYUSERPWD. Guzzle's default handler is cURL, so both behave alike: HTTPS targets go through a CONNECT tunnel, and the ARC username decides pool, country, city and session.

Reviewed 30 Oct 2026

your proxy, as a URL
http://USERNAME_pool-arcfast_country-de:PASSWORD@proxy.arcproxies.io:8000

The short answer

  • Set 'proxy' => 'http://user:pass@host:port' on the client; override it per request for a different session or country.
  • Guzzle's connect_timeout and timeout default to 0, which waits indefinitely. Set both.
  • A refused tunnel throws ConnectException with "CONNECT tunnel failed, response 407"; stop there instead of retrying.
  • With plain cURL, CURLINFO_HTTP_CONNECTCODE gives the proxy's answer to CONNECT.

Setup

Set up PHP Guzzle with a proxy, step by step

  1. 01

    Install Guzzle

    Guzzle 7 needs Composer. The plain cURL examples further down need only the curl extension.

    shell
    composer require guzzlehttp/guzzle:^7
  2. 02

    Send a request and check the exit IP

    Put the credentials in the proxy URL. If the password contains @, : or /, encode it with rawurlencode() first.

    exit_ip.php
    <?phprequire __DIR__ . '/vendor/autoload.php';use GuzzleHttp\Client;$client = new Client([    'proxy'           => 'http://USERNAME_pool-arcfast_country-de:PASSWORD@proxy.arcproxies.io:8000',    'connect_timeout' => 10,    'timeout'         => 30,]);$res = $client->get('https://api.ipify.org', ['query' => ['format' => 'json']]);echo $res->getStatusCode(), ' ', $res->getBody(), PHP_EOL;
  3. 03

    Hold one exit for a task

    Pass a sticky username in the request's own proxy option; it overrides the client's. Every request with the same handle leaves from the same IP for up to _sesstime- minutes.

    sticky.php
    <?phprequire __DIR__ . '/vendor/autoload.php';use GuzzleHttp\Client;function stickyProxy(int $minutes = 10): string{    $handle = bin2hex(random_bytes(4));    return "http://USERNAME_pool-arcfast_country-de_session-{$handle}_sesstime-{$minutes}:PASSWORD@proxy.arcproxies.io:8000";}$client = new Client(['connect_timeout' => 10, 'timeout' => 30, 'http_errors' => false]);$proxy = stickyProxy();foreach ([1, 2, 3] as $page) {    $res = $client->get('https://example.com/search', [        'proxy' => $proxy,        'query' => ['q' => 'lamp', 'page' => $page],    ]);    echo $page, ' ', $res->getStatusCode(), PHP_EOL;}
  4. 04

    Target a country or city

    The array form maps target schemes to proxies and takes a no list of hosts to reach directly. Guzzle fills no from NO_PROXY only when you do not pass your own proxy option.

    geo.php
    <?phprequire __DIR__ . '/vendor/autoload.php';use GuzzleHttp\Client;$markets = [    'de-hamburg'   => 'USERNAME_pool-arcfast_country-de_city-hamburg',    'it-milan'     => 'USERNAME_pool-arcfast_country-it_city-milan',    'au-melbourne' => 'USERNAME_pool-arcfast_country-au_city-melbourne',];foreach ($markets as $market => $user) {    $proxy = "http://{$user}:PASSWORD@proxy.arcproxies.io:8000";    $client = new Client([        'proxy'           => ['http' => $proxy, 'https' => $proxy, 'no' => ['localhost']],        'connect_timeout' => 10,        'timeout'         => 30,        'http_errors'     => false,   // return 4xx/5xx instead of throwing    ]);    echo $market, ' ', $client->get('https://example.com/price/123')->getStatusCode(), PHP_EOL;}
  5. 05

    Retry rate limits, stop on 407

    Middleware::retry takes a decider and a delay in milliseconds. Retry 429, 5xx and connection failures; a refused tunnel is also a ConnectException, so check the message for the 407 first.

    retry.php
    <?phprequire __DIR__ . '/vendor/autoload.php';use GuzzleHttp\Client;use GuzzleHttp\Exception\ConnectException;use GuzzleHttp\HandlerStack;use GuzzleHttp\Middleware;use Psr\Http\Message\RequestInterface;use Psr\Http\Message\ResponseInterface;$stack = HandlerStack::create();$stack->push(Middleware::retry(    function (int $retries, RequestInterface $req, ?ResponseInterface $res = null, ?\Throwable $err = null): bool {        if ($retries >= 3) {            return false;        }        if ($err instanceof ConnectException) {            return !str_contains($err->getMessage(), 'response 407');   // wrong credentials: stop        }        return $res !== null && ($res->getStatusCode() === 429 || $res->getStatusCode() >= 500);    },    fn (int $retries): int => 1000 * 2 ** $retries   // 2 s, 4 s, 8 s));$client = new Client([    'handler'         => $stack,    'proxy'           => 'http://USERNAME_pool-arcfast_country-de:PASSWORD@proxy.arcproxies.io:8000',    'connect_timeout' => 10,    'timeout'         => 30,]);echo $client->get('https://example.com/')->getStatusCode(), PHP_EOL;
  6. 06

    Plain PHP cURL

    Without Guzzle, set CURLOPT_PROXY and CURLOPT_PROXYUSERPWD. After a failure, CURLINFO_HTTP_CONNECTCODE holds the proxy's answer to CONNECT: 407 means the credentials were rejected. Reusing the handle reuses the connection, and with it the exit.

    curl.php
    <?php$ch = curl_init('https://api.ipify.org?format=json');curl_setopt_array($ch, [    CURLOPT_PROXY          => 'http://proxy.arcproxies.io:8000',    CURLOPT_PROXYUSERPWD   => 'USERNAME_pool-arcfast_country-de:PASSWORD',    CURLOPT_RETURNTRANSFER => true,    CURLOPT_CONNECTTIMEOUT => 10,    CURLOPT_TIMEOUT        => 30,]);$body = curl_exec($ch);if ($body === false) {    $proxyStatus = curl_getinfo($ch, CURLINFO_HTTP_CONNECTCODE);   // 407 = bad credentials    exit("failed: " . curl_error($ch) . " (proxy said {$proxyStatus})" . PHP_EOL);}echo curl_getinfo($ch, CURLINFO_HTTP_CODE), ' ', $body, PHP_EOL;

Guzzle proxy option forms

FormExampleUse it for
String on the client'proxy' => 'http://user:pass@host:port'One pool and country for everything
String on a request$client->get($url, ['proxy' => $sticky])A session or market per task
Array['http' => $p, 'https' => $p, 'no' => ['localhost']]Different proxies per scheme, or hosts to skip
SOCKS5'proxy' => 'socks5h://user:pass@host:port'Same gateway and port; DNS resolved at the proxy

Guzzle's documentation shows HTTP proxy URLs; the socks5h:// form is passed through to the cURL handler, and worked against our test gateway. With plain cURL the equivalent is CURLOPT_PROXYTYPE => CURLPROXY_SOCKS5_HOSTNAME.

Connection reuse and rotation in PHP

One Guzzle client keeps its cURL handles and reuses connections: two requests to the same host through the same proxy shared one tunnel, and so one exit, in our test. A request with a different proxy URL opened a new tunnel. To spread requests over exits, give each a different session handle; to keep an exit, reuse the handle. Rotating vs sticky sessions explains the trade-offs, and handling 429 rate limits the backoff.

PHP-FPM workers each run their own process, so a client created per web request starts with no open connections. For crawling, a long-running CLI worker reuses tunnels and saves the handshake each time.

The problem

Common pitfalls

No timeouts

Guzzle's connect_timeout and timeout default to 0, meaning no limit. A stalled tunnel then blocks the worker indefinitely.

Retrying every ConnectException

A refused tunnel is a ConnectException too. Check for "response 407" before retrying, or the retries just repeat the wrong password.

Relying on NO_PROXY with an explicit proxy option

Guzzle only reads NO_PROXY when it builds the proxy from the environment. With your own proxy option, list skipped hosts under no.

An unencoded password in the URL

A @ or : in the password breaks the proxy URL. Use rawurlencode() on it, or with plain cURL pass it through CURLOPT_PROXYUSERPWD.

FAQ

Questions

Something not covered? Write to contact@arcproxies.io.

How do I set a proxy with authentication in Guzzle?

Pass the credentials in the proxy URL: new Client(['proxy' => 'http://user:pass@host:port']). Guzzle hands it to cURL, which sends Basic proxy authentication, including on the CONNECT request for HTTPS sites.

How do I use a different proxy per request in Guzzle?

Pass proxy in the request options: $client->get($url, ['proxy' => $otherProxy]). Request options override the client's defaults, so one client can serve several sessions or countries.

What does "cURL error 7: CONNECT tunnel failed, response 407" mean?

The proxy refused to open the HTTPS tunnel because the credentials did not match. Copy the username and password again from the dashboard and encode special characters. Retrying will not help.

How do I set a proxy in plain PHP cURL?

Set CURLOPT_PROXY to http://host:port and CURLOPT_PROXYUSERPWD to user:pass with curl_setopt_array(). For SOCKS5 with remote DNS, add CURLOPT_PROXYTYPE => CURLPROXY_SOCKS5_HOSTNAME.

Does Guzzle support SOCKS5 proxies?

With the default cURL handler, yes: socks5h://user:pass@host:port in the proxy option is passed to cURL. socks5h makes the proxy resolve the hostname.

Sources & review

Last reviewed by the ARC Proxies team. Prices are read from the live catalog at build time.

  1. [1]Guzzle: Request Options: proxy, timeout, connect_timeout
  2. [2]Guzzle: Middleware::retry (src/Middleware.php)
  3. [3]PHP: curl_setopt
  4. [4]PHP: curl_getinfo

Next

What people build with it

Use caseProxies for web scraping and data collectionA second provider that just works — Scrapy, requests, Crawlee or Playwright, self-serve, no sales call, bandwidth that never expires.Use caseProxies for price and competitor monitoringSee the price a shopper in Berlin, Lyon or Chicago sees. ISP bandwidth at our lowest price per GB for HTML checks, residential where a marketplace is strict.Use caseProxies for real-estate and job-market dataListings and postings are ranked by where the visitor is. Collect them city by city so your index reflects the market, not your server's location.GlossaryHTTP CONNECTHTTP CONNECT is the HTTP request method a client sends to a proxy to ask for a tunnel to a host and port; once the proxy answers with a 2xx status it blindly forwards bytes in both directions, which is how HTTPS passes through an HTTP proxy with TLS intact end to end (RFC 9110, section 9.3.6).GuideRotating vs sticky sessionsRotating proxies change exit IP per connection; sticky sessions hold one IP for a set time. How each works, when to use which, and how to set them up.GuideHandling 429 rate limitsWhat HTTP 429 means, how to read Retry-After, and how to retry politely with exponential backoff, jitter and per-host concurrency limits. With code.IntegrationcurlSend curl through an authenticated proxy with -x and -U, check the exit IP, test sticky sessions and countries, use socks5h, and read 407 failures.IntegrationRubyUse an authenticated proxy in Ruby: Net::HTTP proxy arguments, Faraday proxy options, sticky sessions per task, geo-targeting and 407 handling.

Integrations

Other http clients

Python requestsUse an authenticated proxy with Python requests: the proxies argument, sticky sessions per task, geo-targeting, SOCKS5 and retries that skip 407 errors.HTTPXConfigure an authenticated proxy in HTTPX 0.28: the proxy= argument that replaced proxies=, mounts per domain, async clients, SOCKS5 and error handling.aiohttpUse an authenticated proxy with aiohttp: proxy= per request or per session, why proxy_auth is deprecated, SOCKS5 through aiohttp-socks, and 407 handling.AxiosUse an authenticated proxy with Axios in Node.js: the proxy option on 1.16.1+, https-proxy-agent for older versions, sticky sessions, SOCKS5 and 407s.Node.js fetchNode's built-in fetch has no proxy option. Use undici's ProxyAgent as the dispatcher, or NODE_USE_ENV_PROXY, with sticky sessions, SOCKS5 and 407 handling.curlSend curl through an authenticated proxy with -x and -U, check the exit IP, test sticky sessions and countries, use socks5h, and read 407 failures.GoRoute Go's net/http through an authenticated proxy: Transport.Proxy with url.UserPassword, a sticky session per request context, SOCKS5 and 407 checks.RubyUse an authenticated proxy in Ruby: Net::HTTP proxy arguments, Faraday proxy options, sticky sessions per task, geo-targeting and 407 handling.Java HttpClientUse an authenticated proxy with java.net.http.HttpClient: ProxySelector, Authenticator, the Basic-over-CONNECT setting, sticky sessions and 407s.C# HttpClientUse an authenticated proxy in .NET: WebProxy and NetworkCredential on SocketsHttpHandler, a client per sticky session, SOCKS5 and 407 handling.Rust reqwestRoute Rust reqwest through an authenticated proxy: Proxy::all with basic_auth, a Client per sticky session, socks5h, timeouts and 407 errors.wgetRun GNU Wget through an authenticated proxy: https_proxy, --proxy-user, a .wgetrc for credentials, sticky sessions for mirrors and exit codes.PostmanSend Postman requests through an authenticated proxy: the custom proxy settings, Proxy auth, checking the exit IP, sessions, countries and Newman.DockerGive Docker containers and builds an authenticated proxy: HTTP_PROXY, HTTPS_PROXY and NO_PROXY, build args, Compose, and why the daemon proxy differs.

Get started

Get credentials and paste them in.

Sign up, buy a gigabyte, generate a username in the dashboard. Code WELCOME15 takes 15% off your first purchase; bandwidth never expires.