Skip to content
1 GB of residential proxies, free. $0 today, card required.
Handler routing runbook

PHP Guzzle and cURL Proxy Setup: Options, SOCKS5, Errors

Route PHP cURL and Guzzle through an authenticated proxy: CURLOPT settings, Guzzle proxy arrays, the CLI-only HTTP_PROXY rule, SOCKS5 DNS, exit checks and cURL error 56, 28 and 6 fixes.

PHP Guzzle and cURL → DatabayTwo PHP control surfaces, one libcurl route
PHP Guzzle and cURLHTTP client library
Databay gateway modesClient support is covered below
gw.databay.co:8888
By Databay Research Team11 min runbook

Operating principle

Keep the gateway in the proxy string, the credential in CURLOPT_PROXYUSERPWD, and read every failure as a cURL error number on a named hop.

PHP cURL and Guzzle Proxy Settings at a Glance

PHP reaches a proxy through two control surfaces: the cURL extension, configured with curl_setopt() and CURLOPT_* constants, and Guzzle, whose proxy request option becomes libcurl settings on its default cURL handler. Guzzle's FAQ says it uses cURL whenever the extension is available, so both paths share one library, one set of error numbers and one set of proxy rules.

Databay's gateway is gw.databay.co on port 8888 for HTTP proxying, HTTPS CONNECT tunnelling and SOCKS5, with no UDP relay; the username and password come from the Databay dashboard. Use it only for destinations you own or are permitted to access, and follow each destination's terms.

Tool controlDatabay valueWhat to verify
Guzzle proxy stringhttp://gw.databay.co:8888Covers every scheme; no exemption list
Guzzle proxy array keys http, httpsThe same gateway under both keysKeys name the destination scheme; a missing key means no proxy
Guzzle proxy key no[] for proof runsListed hosts go direct
Guzzle curl keyCURLOPT_PROXYUSERPWD from the environmentApplies on the cURL handler only
Guzzle connect_timeout, timeout5, 20The documented default 0 waits indefinitely
CURLOPT_PROXYgw.databay.co:8888No port means 1080; '' disables proxying
CURLOPT_PROXYPORT8888, only if absent from CURLOPT_PROXYlibcurl discourages this option
CURLOPT_PROXYUSERPWDUSER:PASS, each part through rawurlencode()libcurl URL-decodes both parts
CURLOPT_PROXYTYPECURLPROXY_HTTP, CURLPROXY_SOCKS5 or CURLPROXY_SOCKS5_HOSTNAMEOne type per handle, no conflicting scheme prefix
CURLOPT_HTTPPROXYTUNNELUnsettrue tunnels plain HTTP too
CURLOPT_CONNECTTIMEOUT, CURLOPT_TIMEOUT5, 20Defaults are 300 and 0, no transfer limit
verify, CURLOPT_SSL_VERIFYPEERDefault trueNever switch off; use a CA bundle for a private CA

Raw PHP cURL Proxy Request With CURLOPT Options

Keep the gateway, the credential and the destination in separate options, read the credential from the environment, and bound both the connection phase and the transfer:

<?php
$user = getenv('DATABAY_PROXY_USER');
$pass = getenv('DATABAY_PROXY_PASS');
if ($user === false || $pass === false) {
    fwrite(STDERR, "Set DATABAY_PROXY_USER and DATABAY_PROXY_PASS\n");
    exit(1);
}

$ch = curl_init('https://api.ipify.org?format=json');
curl_setopt_array($ch, [
    CURLOPT_PROXY          => 'gw.databay.co:8888',
    CURLOPT_PROXYTYPE      => CURLPROXY_HTTP,
    CURLOPT_PROXYUSERPWD   => rawurlencode($user) . ':' . rawurlencode($pass),
    CURLOPT_NOPROXY        => '',  // proof run: ignore inherited NO_PROXY
    CURLOPT_CONNECTTIMEOUT => 5,
    CURLOPT_TIMEOUT        => 20,
    CURLOPT_RETURNTRANSFER => true,
]);

$body = curl_exec($ch);
printf("errno=%d connect_code=%d http_code=%d\n", curl_errno($ch),
    curl_getinfo($ch, CURLINFO_HTTP_CONNECTCODE),
    curl_getinfo($ch, CURLINFO_RESPONSE_CODE));
echo $body === false ? curl_error($ch) : $body, "\n";

CURLOPT_PROXY carries the port. libcurl falls back to 1080 without one and discourages the separate CURLOPT_PROXYPORT, so use that field only as the single place the port appears. CURLOPT_PROXYTYPE defaults to CURLPROXY_HTTP; stating it makes the SOCKS5 change a one-line diff.

libcurl URL-decodes both halves of CURLOPT_PROXYUSERPWD, so pass each part through rawurlencode(); otherwise a % sign, or a colon in the username, changes the credential and earns a 407. CURLOPT_PROXYAUTH already defaults to CURLAUTH_BASIC. The PHP cURL constants reference lists every option's default.

For https:// URLs libcurl opens a CONNECT tunnel by itself, so CURLOPT_HTTPPROXYTUNNEL is unnecessary; true forces a tunnel for plain http:// too, and libcurl notes that proxies often limit which ports accept CONNECT.

CURLINFO_HTTP_CONNECTCODE is the gateway's answer to CONNECT, so a 407 there is a proxy-hop refusal before any destination traffic exists. CURLOPT_CONNECTTIMEOUT defaults to 300 seconds and CURLOPT_TIMEOUT to 0, meaning no limit. Never set CURLOPT_SSL_VERIFYPEER to false; point CURLOPT_CAINFO at a private CA bundle instead.

Ready to run this for real?

Credentials appear in your dashboard in minutes, on the same gateway and username format this PHP Guzzle and cURL guide uses.

Guzzle Proxy Option With Credentials From the Environment

Guzzle's request options accept proxy as a string, used for every scheme, or as an array keyed by destination scheme (http, https) plus a no list of hosts to leave unproxied. The key is the destination scheme and the value the proxy URL, so 'https' => 'http://gw.databay.co:8888' is correct. A scheme without a key gets no proxy from Guzzle.

<?php
require __DIR__ . '/vendor/autoload.php';

use GuzzleHttp\Client;
use GuzzleHttp\Exception\TransferException;

$user = getenv('DATABAY_PROXY_USER');
$pass = getenv('DATABAY_PROXY_PASS');
if ($user === false || $pass === false) {
    fwrite(STDERR, "Set DATABAY_PROXY_USER and DATABAY_PROXY_PASS\n");
    exit(1);
}

$gateway = 'http://gw.databay.co:8888';  // no secret in this string

$client = new Client([
    'proxy' => ['http' => $gateway, 'https' => $gateway, 'no' => []],
    'curl'  => [
        CURLOPT_PROXYUSERPWD => rawurlencode($user) . ':' . rawurlencode($pass),
        CURLOPT_NOPROXY      => '',  // proof run: ignore inherited NO_PROXY
    ],
    'connect_timeout' => 5,
    'timeout'         => 20,
]);

try {
    $response = $client->request('GET', 'https://api.ipify.org?format=json');
    echo $response->getStatusCode(), ' ', $response->getBody(), "\n";
} catch (TransferException $e) {
    fwrite(STDERR, get_class($e) . ': ' . $e->getMessage() . "\n");
    exit(1);
}

The proxy string holds no secret. Guzzle's FAQ documents the curl key for raw cURL options, and CURLOPT_PROXYUSERPWD also supplies SOCKS5 credentials, per libcurl's SOCKS5 authentication documentation. Constructor options become defaults for every request, so re-check any request that passes its own proxy or curl option. connect_timeout and timeout default to 0, which waits indefinitely.

Guzzle also documents credentials inside the proxy URL, as in http://USER:PASS@gw.databay.co:8888. If you use that form, compose it at runtime from the same environment variables with rawurlencode(), never as a literal, and treat the string as a secret that stays out of logs, var_dump() output and exception reports. The debug option's verbose trace can include the Proxy-Authorization header too.

Guzzle Environment Variables and the CLI-Only HTTP_PROXY Rule

Without an explicit proxy option, Guzzle reads three variables documented in its quickstart: HTTP_PROXY for requests using the http protocol, HTTPS_PROXY for https, and NO_PROXY for URLs that should not use a proxy. Because HTTP_PROXY may contain arbitrary user input in some CGI environments, Guzzle uses it only on the CLI SAPI. A job that routes correctly from the command line can therefore send http:// requests direct under a web SAPI, while https:// still follows HTTPS_PROXY.

Guzzle fills no from NO_PROXY automatically, but an explicit proxy option makes that list your job, for example 'no' => array_filter(array_map('trim', explode(',', (string) getenv('NO_PROXY')))). Underneath, libcurl applies no_proxy or NO_PROXY whenever CURLOPT_NOPROXY is unset, even when a proxy is specified, so an inherited NO_PROXY=* can send traffic direct. The proof runs set CURLOPT_NOPROXY to an empty string, which enables the proxy for all hostnames. Log variable names, never values.

SOCKS5 and socks5h From Guzzle and PHP cURL

SOCKS5 shares the gateway port, but support comes from the libcurl build behind PHP, so record curl_version() first; PHP 8.4 adds a feature_list array to it. libcurl accepts socks5:// and socks5h:// prefixes since 7.21.7, and CURLPROXY_SOCKS5_HOSTNAME needs cURL 7.18.0.

printf("libcurl %s\n", curl_version()['version']);
$credential = rawurlencode($user) . ':' . rawurlencode($pass);

// Guzzle: SOCKS5 with gateway-side DNS for every scheme
$socks = new GuzzleHttp\Client([
    'proxy'           => 'socks5h://gw.databay.co:8888',
    'curl'            => [CURLOPT_PROXYUSERPWD => $credential],
    'connect_timeout' => 5,
    'timeout'         => 20,
]);

// Raw cURL: on the handle from the HTTP example, change only the type
curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5_HOSTNAME);

socks5h:// and CURLPROXY_SOCKS5_HOSTNAME send the hostname for the proxy to resolve. socks5:// and CURLPROXY_SOCKS5 resolve it on the PHP host, so local DNS sees each destination and a lookup failure becomes cURL error 6. Use one mechanism per handle. SOCKS5 here carries TCP only and adds no encryption; destination TLS still protects HTTPS. See the HTTP versus SOCKS5 comparison and the cURL command-line guide for the same modes from a shell.

Verify the Exit Before the Real Destination

A completed request proves nothing about the route. Before calling the destination you are authorized to use:

  1. Request https://api.ipify.org?format=json and record the address, UTC time, libcurl version and username variant, never the password.
  2. Take a second reading from https://databay.com/what-is-my-ip/json or the What Is My IP page.
  3. Repeat the raw request with CURLOPT_PROXY set to an empty string, which libcurl documents as disabling every proxy; if that direct address matches, the gateway was not used.
  4. Then send one request to the real destination, without retries.

CURLINFO_PRIMARY_IP is the address of the most recent connection, which is the gateway, not the exit. An observed exit is point-in-time evidence, not proof of anonymity or future routing.

Troubleshoot cURL Errors 56, 28 and 6 by Hop

Guzzle puts the cURL error number in its exception message and raw cURL returns it from curl_errno(); the libcurl error reference defines each one. Classify by hop: the proxy hop is reaching gw.databay.co, proxy authentication and the CONNECT or SOCKS5 handshake; the destination hop is TLS and the HTTP status.

EvidenceHopNext safe check
Error 5Proxy: gateway name lookupCheck the spelling and the local resolver
Error 7Proxy: TCPConfirm port 8888 and outbound access
Error 56, CONNECT code 407Proxy: authentication, https:// URLRe-copy the dashboard username, re-check rawurlencode(), confirm the credential reached the request
ClientException with 407Proxy: authentication, http:// URLSame checks; without a tunnel the 407 is a normal response
Error 56, other CONNECT codeProxy: tunnel refusedKeep the code; never switch sessions to get around a refusal
Error 97Proxy: SOCKS5 handshake (libcurl 7.73.0+)Check type, credential and address mode
Error 28The phase that ran out of budgetCompare elapsed time with both timeouts
Error 6Destination resolved on the PHP hostLook for a no or NO_PROXY match, a missing scheme key, HTTP_PROXY outside the CLI, or socks5://
Error 35 or 60Destination: TLSFix the CA bundle; keep verification on
401, 403 or 429Destination: access policyStop and honor Retry-After

Error 56 wording varies by libcurl version, so branch on numbers. libcurl counts DNS and protocol handshakes in the connection phase, and the total timeout includes it: an expiry near the connect budget implicates the gateway path, one near the total budget a slow tunnel or destination. Never turn off verify or CURLOPT_SSL_VERIFYPEER to clear an error; inside a CONNECT tunnel, TLS runs between PHP and the destination.

Add Databay Username Controls After the Base Route Works

Prove the base credential, then change one username control at a time, copying the complete username from the Databay dashboard rather than inventing grammar for other pools. The documented residential examples are USER-zone-residential-countryCode-us and, with a sticky session, USER-zone-residential-countryCode-us-sessionId-abc123.

Premium Residential targets one of country, state, city, ZIP, coordinate or ASN at a time; Residential Flex, Datacenter and Mobile target country or continent. All pools are shared. A sticky session requests continuity for up to 1440 minutes on Premium Residential and Residential Flex where available, or up to 120 minutes on Datacenter and Mobile. The IP can change before the window ends. A new proxy connection may select an available exit, while connection or tunnel reuse, including a reused cURL handle or Guzzle client, may keep the same exit; a different exit is not promised. Traffic is billed per GB.

Pick Premium Residential, Residential Flex, Datacenter or Mobile by the targeting the authorized job needs, and stop on CAPTCHA, account enforcement, 403 or 429.

Credential Hygiene and a PHP Release Checklist

  • Inject DATABAY_PROXY_USER and DATABAY_PROXY_PASS from a secret store at runtime; never commit them to source, .env files or images.
  • Keep the credential in CURLOPT_PROXYUSERPWD so proxy strings stay secret-free.
  • Keep debug and CURLOPT_VERBOSE off in shared environments; never share traces, dumps or screenshots of proxy settings.
  • Redact exception messages before log aggregation and rotate the password after any exposure.

Preflight each environment:

  1. Record PHP_VERSION, the Guzzle version and curl_version().
  2. Confirm ext-curl is loaded, so the curl key applies.
  3. List proxy variables in the target SAPI by name only.
  4. Run the exit check and the direct control.
  5. Set both timeouts everywhere and keep verification on.
  6. Agree stop conditions for 401, 403, 429 and CAPTCHA.
Troubleshooting desk

Questions specific to PHP Guzzle and cURL

How do I set a proxy in PHP cURL?
Set CURLOPT_PROXY to gw.databay.co:8888, CURLOPT_PROXYTYPE to CURLPROXY_HTTP and CURLOPT_PROXYUSERPWD to the username and password read from the environment, each passed through rawurlencode(). Add CURLOPT_CONNECTTIMEOUT and CURLOPT_TIMEOUT, then verify the exit against an IP echo endpoint before calling the real destination.
Why does Guzzle ignore my HTTP_PROXY variable?
Guzzle documents that HTTP_PROXY is only used on the CLI SAPI, because the variable may contain arbitrary user input in some CGI environments. Under a web SAPI, http:// requests go direct unless you pass an explicit proxy option. HTTPS_PROXY carries no such restriction in Guzzle's documentation.
What does cURL error 56 with a 407 mean in Guzzle?
For an https:// destination, the gateway refused proxy authentication before the CONNECT tunnel opened, and libcurl reported it as a receive failure. Check the complete dashboard username, the password, the rawurlencode() step and whether the credential reached that request. In raw cURL, CURLINFO_HTTP_CONNECTCODE returns the 407.
Should I use socks5:// or socks5h:// in PHP?
Use socks5h:// in a proxy string, or CURLPROXY_SOCKS5_HOSTNAME with CURLOPT_PROXYTYPE, when the gateway should resolve destination names. socks5:// and CURLPROXY_SOCKS5 resolve names on the PHP host first, so local DNS sees each destination and a local lookup failure appears as cURL error 6.
Do I need CURLOPT_HTTPPROXYTUNNEL for HTTPS URLs?
No. libcurl tunnels https:// destinations through an HTTP proxy with CONNECT on its own. Setting the option to true forces a tunnel for every operation, plain http:// URLs included, and some proxies accept CONNECT only on particular ports.
Can I turn off certificate verification to fix a proxy SSL error?
No. Inside a CONNECT tunnel the TLS session runs between PHP and the destination, so a certificate error belongs to the destination or the local trust store. Point Guzzle's verify option or CURLOPT_CAINFO at the correct CA bundle and keep verification enabled.
Adjacent runbooks

Same gateway, different control surface

  • cURL

    Command-line client

    curl -> proxy auth -> CONNECT or SOCKS -> approved destination

  • Python Requests

    HTTP client library

    Session -> HTTPAdapter policy -> proxy hop -> approved destination

  • Axios

    HTTP client library

    Axios request -> route selector -> absolute-form or CONNECT -> hop evidence

  • .NET HttpClient

    HTTP client library

    HttpClient -> SocketsHttpHandler.Proxy -> CONNECT or SOCKS5 -> observed exit

  • Go net/http

    HTTP client library

    http.Client -> Transport.Proxy -> CONNECT or SOCKS5 -> observed exit

  • Java HttpClient

    HTTP client library

    HttpClient.Builder -> ProxySelector.of -> Authenticator on 407 -> observed exit

Pick the network once the route works. Residential for home-connection exits with state, city, ZIP and ASN targeting; datacenter for fast server IPs on public websites. Switching is one word in the username.

Residential →Datacenter →

Where teams run PHP Guzzle and cURL behind the gateway: Proxies for web scraping · Proxies for market research

Ship PHP Guzzle and cURL to production

Create an account, drop in your gateway credentials, and route your first PHP Guzzle and cURL request in minutes.

Pricing, order minimums, and traffic validity vary by product.