DEV Community

ProxyMaster
ProxyMaster

Posted on

Using Proxies in PHP: Raw cURL and Guzzle

WinGate private IPv4 and SOCKS5 proxies, free 2 hour test

PHP still powers a huge amount of server-side scraping and integration work, and sooner or later you need to route a request through a proxy. Whether you are on raw cURL or on Guzzle, the setup is straightforward once you know where the options go. Here is a practical guide for both, including the auth and HTTPS details that quietly break things.

Raw cURL in PHP

PHP's cURL extension mirrors the command-line tool. You set the proxy with CURLOPT_PROXY and, if needed, credentials with CURLOPT_PROXYUSERPWD.

$ch = curl_init("https://example.com");
curl_setopt_array($ch, [
    CURLOPT_PROXY => "proxy.host:8080",
    CURLOPT_PROXYUSERPWD => "user:pass",
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 20,
]);
$body = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
Enter fullscreen mode Exit fullscreen mode

For a SOCKS5 proxy, set the type explicitly so cURL does not assume HTTP:

curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5_HOSTNAME);
Enter fullscreen mode Exit fullscreen mode

SOCKS5_HOSTNAME resolves DNS at the proxy, the PHP equivalent of socks5h, so your lookups do not leak locally.

Guzzle

Guzzle wraps cURL but exposes a cleaner API. The proxy goes in the request options, and you can put credentials right in the URL.

use GuzzleHttp\Client;

$client = new Client([
    "proxy" => "http://user:pass@proxy.host:8080",
    "timeout" => 20,
]);

$res = $client->get("https://example.com");
echo $res->getStatusCode();
Enter fullscreen mode Exit fullscreen mode

Guzzle also lets you set different proxies per scheme, which is handy when you want HTTP and HTTPS handled distinctly:

"proxy" => [
    "http"  => "http://user:pass@proxy.host:8080",
    "https" => "http://user:pass@proxy.host:8080",
    "no"    => ["localhost"],
],
Enter fullscreen mode Exit fullscreen mode

The details that break silently

  • URL-encode special characters in the password. An @ or : in a raw proxy string splits the URL in the wrong place and produces a confusing failure.
  • Verify TLS. Do not disable CURLOPT_SSL_VERIFYPEER to "make it work." A tunnel problem is a proxy or agent issue; turning off verification hides real errors and creates a security hole.
  • Set timeouts. Without CURLOPT_TIMEOUT, a dead proxy hangs your worker indefinitely.

Rotation from PHP

PHP scrapers usually run as workers or cron jobs, so the clean pattern is to point every request at one rotating endpoint and let the pool cycle exits. You keep one proxy string in config, and each request lands on a fresh address without you maintaining a list. When a job must hold a session, pin it to a stable exit instead.

What the pool needs

WinGate fits PHP scraping directly: private IPv4 proxies with SOCKS5 and standard auth, so CURLOPT_PROXYUSERPWD and Guzzle's URL credentials both just work. The rotating pool cycles exits from a worldmix, traffic is unlimited so long-running workers do not meter you, and it speaks HTTP, HTTPS, and SOCKS5, so raw cURL and Guzzle share the same account. There is a free 2 hour test, so wire it into a small script and confirm the exit rotates before you commit.

An honest note: the proxy config stops the address from being the reason your PHP scraper gets blocked, it does not hide automation or exempt you from a site's terms. Pacing and rotation still do the real anti-blocking work.

The takeaway: in raw cURL use CURLOPT_PROXY and CURLOPT_PROXYUSERPWD, set SOCKS5_HOSTNAME for SOCKS, in Guzzle put the proxy in request options, encode special characters, keep TLS verification on, and route everything through a rotating pool. Do that and PHP handles proxied requests as cleanly as anything else.

Top comments (0)