Geonode logo
Geonode Team

Geonode Team

Atualizado: 16 de outubro de 2026

Publicado: 16 de outubro de 2026

Configuração do Guzzle Proxy em PHP: Autenticação, HTTPS e Resolução de Problemas

Este guia explica detalhadamente todas as partes de uma configuração funcional de proxy no Guzzle: a opção de pedido `proxy` nas suas formas de cadeia de caracteres e de matriz, URLs de proxy autenticadas, destinos HTTPS através de um túnel CONNECT, tempos limite e verificação de certificados, bem como as exceções que o Guzzle lança quando o proxy recusa o pedido. No final, terá um cliente PHP reutilizável que encaminha o tráfego através de um proxy residencial, mantém um único IP de saída ao longo de uma sessão, verifica esse IP e tenta novamente com uma nova sessão quando uma solicitação falha. Todos os exemplos destinam-se ao Guzzle 7 e ao PHP 8.1 ou posterior. Instale com o `composer require guzzlehttp/guzzle:^7`.

Este guia explica detalhadamente todas as partes de uma configuração funcional de proxy no Guzzle: a opção de pedido proxy nas suas formas de cadeia de caracteres e de matriz, URLs de proxy autenticadas, destinos HTTPS através de um túnel CONNECT, tempos limite e verificação de certificados, bem como as exceções que o Guzzle lança quando o proxy recusa o pedido. No final, terá um cliente PHP reutilizável que encaminha o tráfego através de um proxy residencial, mantém um único IP de saída ao longo de uma sessão, verifica esse IP e tenta novamente com uma nova sessão quando uma solicitação falha.

Todos os exemplos destinam-se ao Guzzle 7 e ao PHP 8.1 ou posterior. Instale com o composer require guzzlehttp/guzzle:^7.

A opção proxy: cadeia de caracteres ou matriz

O Guzzle configura um proxy através da opção de pedido proxy, documentada em docs.guzzlephp.org. Pode passá-la por pedido ou no construtor do cliente como valor predefinido. A forma mais simples é uma cadeia de caracteres, que se aplica a todos os protocolos.

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

use GuzzleHttp\Client;

$client = new Client([
    'proxy'   => 'http://USERNAME:PASSWORD@premium-residential.geonode.com:9000',
    'timeout' => 30,
]);

$response = $client->get('https://api.ipify.org?format=json');
echo $response->getBody(), PHP_EOL;

A forma de matriz permite-lhe escolher um proxy diferente por esquema de URI e excluir anfitriões. São reconhecidas três chaves: http, https e no.

<?php
$proxyUrl = 'http://USERNAME:PASSWORD@premium-residential.geonode.com:9000';

$client = new GuzzleHttp\Client([
    'proxy' => [
        'http'  => $proxyUrl,            // used for http:// targets
        'https' => $proxyUrl,            // used for https:// targets (CONNECT tunnel)
        'no'    => ['localhost', '.internal.example'], // never proxied
    ],
]);

Utilize a forma de cadeia de caracteres quando tudo tiver de passar por um único proxy. Utilize a forma de matriz quando precisar da lista no ou quiser que as solicitações HTTP simples passem por um conjunto de servidores de um centro de dados, enquanto as HTTPS passam por servidores residenciais.

A lista no e a ressalva NO_PROXY

O Guzzle lê HTTP_PROXY, HTTPS_PROXY e NO_PROXY a partir do ambiente quando não define uma opção proxy. No momento em que define uma, essa automatização cessa, incluindo a lista de exclusão. A documentação oficial é explícita ao indicar que cabe então a si passar o no. Se o seu contentor definir o NO_PROXY para serviços internos, esquecer-se disto significa que essas chamadas passam subitamente pelo proxy residencial.

$no = getenv('NO_PROXY') ? explode(',', getenv('NO_PROXY')) : [];

$client = new GuzzleHttp\Client([
    'proxy' => [
        'http'  => $proxyUrl,
        'https' => $proxyUrl,
        'no'    => $no,
    ],
]);

Autenticação por proxy no Guzzle: credenciais na URL

O Guzzle não dispõe de uma opção separada como proxy_auth. As credenciais são inseridas na URL do proxy como userinfo (http://username:password@host:port), e o manipulador cURL envia-as como Proxy-Authorization: Basic. Daí decorrem duas regras práticas.

Primeiro, codifique com percentagem qualquer elemento da palavra-passe que não seja seguro para URL. Um @, : ou / numa palavra-passe em bruto impede a análise da URL e resulta num erro 407 confuso ou numa ligação ao anfitrião errado. Construa a URL com rawurlencode() em vez de concatenação de cadeias de caracteres.

function proxyUrl(string $user, string $pass, int $port = 9000): string
{
    return sprintf(
        'http://%s:%s@premium-residential.geonode.com:%d',
        rawurlencode($user),
        rawurlencode($pass),
        $port
    );
}

Segundo, mantenha as credenciais fora do código. Leia-as a partir de variáveis de ambiente ou de um gestor de segredos e nunca registe o URL completo do proxy; a saída do Guzzle, debug, inclui o cabeçalho Proxy-Authorization, por isso, oculte-o antes de enviar registos para qualquer lugar.

Alvos HTTPS através de um proxy HTTP CONNECT

Um ponto comum de confusão: o esquema de URL do proxy descreve a forma como se comunica com o proxy, e não o que o alvo utiliza. Para um destino https://, o Guzzle (através do cURL) abre uma ligação HTTP simples ao proxy, envia CONNECT host:443 e, em seguida, realiza o handshake TLS com o destino dentro desse túnel. Assim, tanto a chave http como a https na matriz apontam normalmente para um URL de proxy http://. Escrever https://premium-residential.geonode.com:9000 indicaria ao cURL para envolver o próprio salto do proxy em TLS, o que o ponto final não espera.

Como o TLS funciona de ponta a ponta dentro do túnel, o proxy nunca vê nem altera o corpo da resposta, e a verificação normal do certificado aplica-se ao site de destino exatamente como aconteceria sem um proxy.

verify e problemas com certificados

A opção verify controla as verificações de certificados para o destino. Deixe-a no valor predefinido true, que utiliza o pacote de CAs do sistema. Se o PHP não conseguir encontrar um pacote (algo comum em imagens Docker mínimas), aponte verify para um ficheiro PEM em vez de a desativar.

$client = new GuzzleHttp\Client([
    'proxy'  => $proxyUrl,
    'verify' => '/etc/ssl/certs/ca-certificates.crt', // or true for the system default
]);

Definir o verify => false para silenciar um erro que só apareceu após a adição de um proxy é um erro; um túnel CONNECT configurado corretamente não causa erros de certificado, pelo que o verdadeiro problema está noutro local, normalmente na confusão dos esquemas https:// acima referida.

Tempos de espera: timeout e connect_timeout

O valor predefinido do Guzzle para timeout é 0, o que significa «esperar indefinidamente». Através de um proxy residencial, essa é uma má configuração por predefinição, porque um dispositivo de saída lento pode bloquear um trabalhador indefinidamente. Defina dois valores: connect_timeout limita a ligação TCP ao proxy e é suportado apenas pelo manipulador cURL; timeout limita toda a transferência em segundos.

$client = new GuzzleHttp\Client([
    'proxy'           => $proxyUrl,
    'connect_timeout' => 10,   // seconds to reach the proxy
    'timeout'         => 45,   // total seconds for the request
]);

As saídas residenciais são mais lentas do que as dos centros de dados, por isso, defina um tempo limite total mais generoso do que faria para um pedido direto. Um tempo limite de ligação que se esgota constantemente indica um problema no caminho de rede, em vez de um problema no destino.

Tratamento de exceções num proxy Guzzle

O Guzzle lança exceções tipadas, todas derivadas de GuzzleHttp\Exception\RequestException, e saber qual delas ocorreu indica onde se verificou a falha.

ExceçãoQuando é lançadaCausa típica no proxy
ConnectExceptionAusência total de resposta HTTPHost do proxy inacessível, porta errada, tempo de espera de ligação esgotado, falha de DNS
ClientExceptionResposta 4xx407: credenciais incorretas; 403/429 do destino
ServerExceptionResposta 5xxErro no destino ou o proxy não conseguiu aceder ao destino
RequestExceptionTodos os outros casosTempo limite total após os cabeçalhos, falha de TLS

Vale a pena isolar um código 407. É o proxy, e não o destino, que está a recusar o pedido, pelo que repetir a tentativa não ajudará até que as credenciais sejam alteradas.

<?php
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Exception\ServerException;
use GuzzleHttp\Exception\RequestException;

try {
    $response = $client->get('https://example.com/');
} catch (ConnectException $e) {
    // Could not reach the proxy or the connect_timeout expired.
    error_log('connect: ' . $e->getMessage());
} catch (ClientException $e) {
    $status = $e->getResponse()->getStatusCode();
    if ($status === 407) {
        // Proxy rejected credentials: check username grammar and password.
        throw new RuntimeException('proxy auth failed', 407, $e);
    }
    // 403, 404, 429 etc. come from the target site.
} catch (ServerException $e) {
    // 5xx from the target (or the proxy failing to reach it).
} catch (RequestException $e) {
    // Timeouts after connect, TLS errors and other transfer failures.
}

Desativar exceções com http_errors

Se preferir ramificar com base no código de estado em vez de capturar exceções, defina http_errors => false. O Guzzle devolve então a resposta para qualquer estado, incluindo o 407, e você próprio verifica o getStatusCode(). O ConnectException continua a ser lançado porque não há resposta para devolver.

$response = $client->get('https://example.com/', ['http_errors' => false]);

if ($response->getStatusCode() === 407) {
    // handle auth failure without an exception
}

Diagnóstico: on_stats e debug

Quando um pedido de proxy do Guzzle é lento ou falha de forma intermitente, duas opções permitem saber o que aconteceu na rede. O on_stats recebe um objeto GuzzleHttp\TransferStats após cada transferência, com o tempo total e as estatísticas do manipulador cURL, tais como a pesquisa de nomes, a ligação e os tempos de TLS.

use GuzzleHttp\TransferStats;

$client->get('https://example.com/', [
    'on_stats' => function (TransferStats $stats) {
        printf("%s took %.3fs\n", $stats->getEffectiveUri(), $stats->getTransferTime());
        if (!$stats->hasResponse()) {
            var_dump($stats->getHandlerErrorData());
        }
    },
]);

O debug => true ativa o CURLOPT_VERBOSE e apresenta a troca CONNECT, incluindo a linha de resposta do proxy. Utilize-o uma vez para confirmar que o túnel está estabelecido e, em seguida, remova-o: ele escreve credenciais na STDOUT. Passar um fluxo fopen() em vez de true direciona a saída para um ficheiro.

Sessões fixas na porta 10000

A porta 9000 alterna o IP de saída a cada pedido. Para fluxos com várias etapas (iniciar sessão, depois carregar páginas e, por fim, paginar), é necessário utilizar o mesmo IP, o que é proporcionado pela porta 10000. A sessão é selecionada através do nome de utilizador, e não de um cabeçalho: acrescente «-session-XXXXXXXX» seguido de um identificador alfanumérico de 8 caracteres e, opcionalmente, «-lifetime-SECONDS» com valores entre 180 e 86 400. Os sufixos de segmentação, como -country-us, devem ser incluídos na mesma cadeia de caracteres.

<?php
function stickyProxyUrl(string $baseUser, string $pass, string $country = 'us', int $lifetime = 600): string
{
    $session = substr(bin2hex(random_bytes(4)), 0, 8); // 8 alphanumeric chars
    $user = sprintf('%s-country-%s-session-%s-lifetime-%d', $baseUser, $country, $session, $lifetime);

    return sprintf(
        'http://%s:%s@premium-residential.geonode.com:10000',
        rawurlencode($user),
        rawurlencode($pass)
    );
}

$client = new GuzzleHttp\Client([
    'proxy'   => stickyProxyUrl(getenv('GEONODE_USER'), getenv('GEONODE_PASS')),
    'timeout' => 45,
]);

Todas as solicitações provenientes de $client partilham agora um único IP de saída durante dez minutos. Crie um novo cliente (ou passe uma nova opção proxy) quando pretender um IP diferente.

Verificar o IP de saída

Antes de confiar numa sessão, confirme o que o destino irá ver. Aceda a um serviço de eco de IP através do mesmo cliente duas vezes: com uma sessão persistente, ambas as chamadas devolvem o mesmo endereço; com a porta 9000, os endereços diferem.

$first  = json_decode((string) $client->get('https://api.ipify.org?format=json')->getBody(), true);
$second = json_decode((string) $client->get('https://api.ipify.org?format=json')->getBody(), true);

printf("exit IP: %s (stable: %s)\n", $first['ip'], $first['ip'] === $second['ip'] ? 'yes' : 'no');

Um pequeno auxiliar de nova tentativa que alterna a sessão

As saídas residenciais podem desaparecer a meio da sessão e, por vezes, os destinos bloqueiam um endereço IP específico. Uma abordagem pragmática consiste em repetir uma solicitação falhada com um novo ID de sessão, o que altera o IP de saída e evita a reutilização de uma ligação que já se encontra num estado anómalo. O auxiliar abaixo recria o cliente a cada tentativa e só repete a solicitação em caso de falhas de ligação, tempos de espera esgotados e códigos de estado que indiquem bloqueio.

<?php
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Exception\RequestException;
use Psr\Http\Message\ResponseInterface;

function fetchWithNewSession(string $url, int $attempts = 3): ResponseInterface
{
    $lastError = null;

    for ($i = 1; $i <= $attempts; $i++) {
        $client = new Client([
            'proxy'           => stickyProxyUrl(getenv('GEONODE_USER'), getenv('GEONODE_PASS')),
            'connect_timeout' => 10,
            'timeout'         => 45,
            'http_errors'     => false,
        ]);

        try {
            $response = $client->get($url);
            $status   = $response->getStatusCode();

            if ($status === 407) {
                throw new RuntimeException('proxy credentials rejected'); // do not retry
            }
            if (!in_array($status, [403, 429, 502, 503, 504], true)) {
                return $response;
            }
            $lastError = new RuntimeException("status $status");
        } catch (ConnectException | RequestException $e) {
            $lastError = $e;
        }

        usleep(500_000 * $i); // simple linear backoff
    }

    throw new RuntimeException("gave up after $attempts attempts", 0, $lastError);
}

O mesmo código funciona com a porta 9000 se retirar o sufixo da sessão, uma vez que cada tentativa obtém um novo IP de qualquer forma. Mantenha o número de tentativas baixo: um destino que responde com um 403 três vezes seguidas está a indicar algo sobre a identificação da solicitação, e não sobre o proxy.

Erros comuns

Utilização de um esquema https:// no URL do proxy

O salto do proxy é em HTTP simples; o HTTPS do destino é executado dentro do túnel CONNECT. Ambas as chaves da matriz devem conter um URL de proxy http://. O sintoma habitual é um ConnectException que menciona um handshake SSL com o anfitrião do proxy.

Esquecer a lista no após definir o proxy

Assim que passar o proxy, o NO_PROXY é ignorado. As verificações de integridade internas, os pontos finais de metadados e as bases de dados acedidas via HTTP passarão pelo proxy residencial e falharão ou, pior ainda, sofrerão fugas de informação. Passe sempre o no explicitamente.

Deixar o timeout em zero

Por predefinição, o Guzzle espera indefinidamente. Uma única ligação residencial bloqueada pode reter um trabalhador da fila até que um supervisor o encerre. Defina tanto o connect_timeout como o timeout em todos os clientes proxy.

Repetir a tentativa após um erro 407

Um erro 407 significa que o proxy rejeitou as credenciais. Repetir a tentativa com o mesmo nome de utilizador e palavra-passe só faz perder tempo. Detete rapidamente a falha, registe o nome de utilizador (nunca a palavra-passe) e verifique os sufixos gramaticais.

Próximo passo

A configuração acima funciona sem alterações com os proxies residenciais Geonode: 2,5 milhões de IPs online em mais de 195 países, HTTP, HTTPS e SOCKS5 no mesmo servidor, e sessões persistentes até 24 horas selecionadas inteiramente através do nome de utilizador. A segmentação por país, estado, cidade e ASN utiliza a mesma sintaxe aqui apresentada, pelo que não são necessárias alterações no código quando se restringe a localização de saída. Os planos são pay-as-you-go sem contrato, a partir de 10 GB por 7,92 $ por mês, e um período de teste de 3 dias custa 5 $. Consulte proxies residenciais para obter todos os detalhes ou compare os níveis na página de preços. Para padrões de scraping que vão além da configuração do proxy, o caso de utilização de web scraping abrange o resto do processo.

Perguntas frequentes

Como posso definir um proxy Guzzle para todas as solicitações de um determinado cliente?

Passe a opção de proxy no construtor do cliente, em vez de o fazer por cada pedido. new Client(['proxy' => 'http://user:pass@host:port']) aplica o proxy a todos os pedidos que o cliente envia, e qualquer opção de proxy ao nível do pedido substitui essa configuração. Este é o padrão normal quando todo o tráfego de um trabalhador deve passar pelo mesmo fornecedor.

Por que razão o meu proxy Guzzle funciona para URLs do tipo http://, mas falha para destinos do tipo https://?

Normalmente, a chave https na forma de matriz está em falta ou aponta para um esquema https://. O Guzzle encaminha destinos HTTPS através de um proxy HTTP com CONNECT, pelo que o URL do proxy para a chave https deve continuar a começar por http://. Verifique a forma de matriz e confirme se a porta do proxy aceita CONNECT, o que acontece com as portas Geonode.

O que significa um erro 407 «Proxy Authentication Required» no Guzzle?

O proxy rejeitou as suas credenciais antes de reencaminhar qualquer coisa. O Guzzle gera um erro ClientException com o estado 407, uma vez que se trata de uma resposta 4xx. Verifique a sintaxe do nome de utilizador, a palavra-passe e se os caracteres especiais na palavra-passe estão codificados com o código de percentagem na URL. Com http_errors definido como false, obtém-se o objeto de resposta 407 em vez de uma exceção.

O Guzzle respeita as variáveis de ambiente HTTP_PROXY e NO_PROXY?

Sim, quando não se passa uma opção de proxy, o Guzzle lê HTTP_PROXY, HTTPS_PROXY e NO_PROXY a partir do ambiente. No momento em que passar uma opção de proxy manualmente, o tratamento automático de NO_PROXY é interrompido, pelo que terá de passar a chave no explicitamente, normalmente explode(',', getenv('NO_PROXY')).

Posso manter o mesmo IP de saída em várias solicitações do Guzzle?

Sim, utilize uma sessão persistente. Configure o proxy Guzzle na porta 10000 e adicione -session-XXXXXXXX (8 caracteres alfanuméricos), além de um sufixo opcional -lifetime-SECONDS ao nome de utilizador. Todas as solicitações que reutilizarem esse nome de utilizador partilham um único IP de saída durante o período de validade definido, entre 180 segundos e 24 horas.

Existem limites que deva ter em conta ao fazer scraping através de um proxy Guzzle?

A largura de banda residencial é cobrada por gigabyte, pelo que páginas HTML e imagens de grande dimensão acumulam dados. As sessões persistentes duram, no máximo, 24 horas e podem terminar mais cedo se o dispositivo a montante se desligar; por isso, o código deve tolerar uma alteração do IP de saída. Respeite também os termos, as regras para robôs e os limites de taxa do site de destino; um proxy não altera as suas obrigações legais.