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.
gw.databay.co:8888Operating 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 control | Databay value | What to verify |
|---|---|---|
Guzzle proxy string | http://gw.databay.co:8888 | Covers every scheme; no exemption list |
Guzzle proxy array keys http, https | The same gateway under both keys | Keys name the destination scheme; a missing key means no proxy |
Guzzle proxy key no | [] for proof runs | Listed hosts go direct |
Guzzle curl key | CURLOPT_PROXYUSERPWD from the environment | Applies on the cURL handler only |
Guzzle connect_timeout, timeout | 5, 20 | The documented default 0 waits indefinitely |
CURLOPT_PROXY | gw.databay.co:8888 | No port means 1080; '' disables proxying |
CURLOPT_PROXYPORT | 8888, only if absent from CURLOPT_PROXY | libcurl discourages this option |
CURLOPT_PROXYUSERPWD | USER:PASS, each part through rawurlencode() | libcurl URL-decodes both parts |
CURLOPT_PROXYTYPE | CURLPROXY_HTTP, CURLPROXY_SOCKS5 or CURLPROXY_SOCKS5_HOSTNAME | One type per handle, no conflicting scheme prefix |
CURLOPT_HTTPPROXYTUNNEL | Unset | true tunnels plain HTTP too |
CURLOPT_CONNECTTIMEOUT, CURLOPT_TIMEOUT | 5, 20 | Defaults are 300 and 0, no transfer limit |
verify, CURLOPT_SSL_VERIFYPEER | Default true | Never 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:
- Request
https://api.ipify.org?format=jsonand record the address, UTC time, libcurl version and username variant, never the password. - Take a second reading from
https://databay.com/what-is-my-ip/jsonor the What Is My IP page. - Repeat the raw request with
CURLOPT_PROXYset to an empty string, which libcurl documents as disabling every proxy; if that direct address matches, the gateway was not used. - 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.
| Evidence | Hop | Next safe check |
|---|---|---|
| Error 5 | Proxy: gateway name lookup | Check the spelling and the local resolver |
| Error 7 | Proxy: TCP | Confirm port 8888 and outbound access |
| Error 56, CONNECT code 407 | Proxy: authentication, https:// URL | Re-copy the dashboard username, re-check rawurlencode(), confirm the credential reached the request |
ClientException with 407 | Proxy: authentication, http:// URL | Same checks; without a tunnel the 407 is a normal response |
| Error 56, other CONNECT code | Proxy: tunnel refused | Keep the code; never switch sessions to get around a refusal |
| Error 97 | Proxy: SOCKS5 handshake (libcurl 7.73.0+) | Check type, credential and address mode |
| Error 28 | The phase that ran out of budget | Compare elapsed time with both timeouts |
| Error 6 | Destination resolved on the PHP host | Look for a no or NO_PROXY match, a missing scheme key, HTTP_PROXY outside the CLI, or socks5:// |
| Error 35 or 60 | Destination: TLS | Fix the CA bundle; keep verification on |
| 401, 403 or 429 | Destination: access policy | Stop 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_USERandDATABAY_PROXY_PASSfrom a secret store at runtime; never commit them to source,.envfiles or images. - Keep the credential in
CURLOPT_PROXYUSERPWDso proxy strings stay secret-free. - Keep
debugandCURLOPT_VERBOSEoff 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:
- Record
PHP_VERSION, the Guzzle version andcurl_version(). - Confirm
ext-curlis loaded, so thecurlkey applies. - List proxy variables in the target SAPI by name only.
- Run the exit check and the direct control.
- Set both timeouts everywhere and keep verification on.
- Agree stop conditions for 401, 403, 429 and CAPTCHA.
Questions specific to PHP Guzzle and cURL
How do I set a proxy in PHP cURL?
Why does Guzzle ignore my HTTP_PROXY variable?
What does cURL error 56 with a 407 mean in Guzzle?
Should I use socks5:// or socks5h:// in PHP?
Do I need CURLOPT_HTTPPROXYTUNNEL for HTTPS URLs?
Can I turn off certificate verification to fix a proxy SSL error?
Same gateway, different control surface
Python Requests
HTTP client library
Session -> HTTPAdapter policy -> proxy hop -> approved destination
.NET HttpClient
HTTP client library
HttpClient -> SocketsHttpHandler.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.
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.