CURLOPT_TIMEOUT = 0: o timeout infinito que travava o gateway de pagamento por 60 segundos
Publicado em 10 de abril de 2026

O sintoma que enganou o diagnóstico
Um gateway de pagamento PHP começou a apresentar lentidão intermitente. O comportamento era estranho: CPU do servidor em 4%, memória estável, sem picos de tráfego. Mas o p99 de latência estava em 5,410s e o máximo registrado chegava a 60,001s. Em 30 minutos de janela, 161 de 1.152 requisições (14%) ultrapassavam 2 segundos.
CPU baixa durante um incidente de lentidão é uma armadilha clássica. Ela descarta a hipótese de processamento intensivo, mas não descarta I/O bloqueante — que consome zero CPU enquanto trava a thread aguardando resposta de um serviço externo.
Os erros 499 (cliente desconectou antes da resposta) apareciam nos logs do nginx, confirmando que usuários abandonavam a requisição antes de receber resposta. O gateway de pagamento estava travando threads PHP-FPM esperando uma resposta do gateway que nunca chegava — ou chegava tarde demais.
A causa: CURLOPT_TIMEOUT = 0 em 6 arquivos
A investigação nos arquivos PHP do gateway revelou a linha em 6 lugares diferentes:
curl_setopt($ch, CURLOPT_TIMEOUT, 0);
// ou equivalentemente:
curl_setopt($ch, CURLOPT_TIMEOUT, 0); // sem timeoutNo cURL, CURLOPT_TIMEOUT = 0 não significa 'sem limite configurado' — significa 'esperar indefinidamente'. É um comportamento contra-intuitivo: o zero não desativa o timeout, ele define o timeout como infinito.
Os 6 arquivos afetados cobriam todos os fluxos críticos do gateway:
Checkout.php — fluxo de checkout padrão
CheckoutTransparent.php — checkout transparente (cartão)
GatewayServices.php — serviços auxiliares
Subscriptions.php — fluxo de assinaturas recorrentes
SessionHandler.php — gestão de sessão do gateway
Refunds.php — estornos e reembolsos
Por que o zero aparece nesses arquivos
O valor 0 é frequentemente copiado de exemplos de documentação ou de código legado onde o desenvolvedor queria dizer 'não quero que o PHP default interfira — deixa o cURL controlar'. A intenção era desativar o timeout do PHP (via max_execution_time) delegando o controle ao cURL. O efeito real foi o oposto: sem nenhum timeout definido no cURL, qualquer request ao gateway que demorasse — por instabilidade da API, latência de rede, sobrecarga temporária — ficava pendurado indefinidamente.
O max 60,001s no percentil máximo não é coincidência: é o timeout padrão do PHP (max_execution_time = 60s) atuando como última defesa. Sem ele, a thread ficaria travada até o reinício do processo.
O diagnóstico: evidências nos logs
Para confirmar a hipótese de I/O bloqueante, os logs do nginx e do PHP-FPM foram cruzados:
# Erros 499 no nginx — cliente desconectou antes da resposta
grep ' 499 ' /var/log/nginx/access.log | awk '{print $7}' | sort | uniq -c | sort -rn
# Workers PHP-FPM presos — status de processos
curl http://localhost/fpm-status?full | grep -E 'state|last request uri|request duration'
# Distribuição de latência das requisições ao gateway de pagamento
grep 'payment-gateway' /var/log/app/requests.log | awk '{print $NF}' | sort -n | awk 'BEGIN{c=0} {a[c++]=$1} END{
print "p90: " a[int(c*0.90)];
print "p95: " a[int(c*0.95)];
print "p99: " a[int(c*0.99)];
print "max: " a[c-1]
}'O status do PHP-FPM mostrava workers em estado 'Reading headers' por dezenas de segundos — esperando resposta do gateway. Com pool configurado para N workers, quando suficientes threads ficavam presas simultaneamente, novas requisições começavam a enfileirar, agravando a lentidão.
A correção: 3 arquivos por vez, validação entre cada lote
// Antes — timeout infinito
curl_setopt($ch, CURLOPT_TIMEOUT, 0);
// Depois — timeout de 5 segundos
curl_setopt($ch, CURLOPT_TIMEOUT, 5);O valor 5 segundos foi escolhido com base no p99 histórico da API do gateway em condições normais (abaixo de 3s) com margem. Requests que legitimamente levam mais de 5s com o gateway são sintoma de problema na API do gateway — não de operação normal — e devem falhar rapidamente para liberar a thread e retornar erro tratável ao usuário.
Importante: CURLOPT_TIMEOUT afeta o tempo total da operação cURL (conexão + transferência). Para controle mais granular, use também CURLOPT_CONNECTTIMEOUT para limitar separadamente o tempo de estabelecimento de conexão.
// Configuração completa recomendada
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 3); // máx 3s para conectar
curl_setopt($ch, CURLOPT_TIMEOUT, 5); // máx 5s totalResultado
Após aplicar o fix nos 6 arquivos e fazer reload do PHP-FPM (sem downtime), os erros 499 zeraram na primeira hora de monitoramento. O p99 voltou para menos de 1 segundo em condições normais. Nenhuma transação de pagamento foi perdida durante a aplicação — o fix foi aplicado arquivo por arquivo com validação entre cada deploy.
Um valor que parece 'desabilitado' (zero) que na verdade significa 'infinito' é o tipo de bug que sobrevive em produção por meses — porque não gera erro explícito, só lentidão intermitente. A única forma de encontrá-lo é procurar ativamente nos arquivos que fazem requests externos.
Como auditar seu código
Para encontrar todos os usos de CURLOPT_TIMEOUT = 0 em um projeto PHP:
# Buscar CURLOPT_TIMEOUT com valor 0 em arquivos PHP
grep -rn 'CURLOPT_TIMEOUT.*0' /var/www/html/ --include='*.php'
# Buscar também a constante numérica (13 = CURLOPT_TIMEOUT)
grep -rn 'CURLOPT_TIMEOUT' /var/www/html/ --include='*.php' | grep -v '//.*CURLOPT_TIMEOUT'
# Verificar se CURLOPT_CONNECTTIMEOUT está ausente nos mesmos arquivos
grep -rL 'CURLOPT_CONNECTTIMEOUT' $(grep -rl 'curl_setopt' /var/www/html/ --include='*.php')Qualquer arquivo que faz requests externos com cURL deve ter explicitamente CURLOPT_TIMEOUT > 0 e CURLOPT_CONNECTTIMEOUT > 0. A ausência dessas configurações em integrações com APIs de pagamento, CEPs, notificações ou qualquer serviço externo é risco direto de disponibilidade.
