Buzeli
buzeliSoluções Digitais
Incidentes

CURLOPT_TIMEOUT = 0: o timeout infinito que travava o gateway de pagamento por 60 segundos

Publicado em 10 de abril de 2026

CURLOPT_TIMEOUT = 0 travando gateway Pagar.me por até 60 segundos — latência p99 5,4s, max 60s, fix em 6 arquivos

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:

Copiar
curl_setopt($ch, CURLOPT_TIMEOUT, 0);
// ou equivalentemente:
curl_setopt($ch, CURLOPT_TIMEOUT, 0);  // sem timeout

No 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:

Copiar
# 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

Copiar
// 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.

Copiar
// Configuração completa recomendada
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 3);  // máx 3s para conectar
curl_setopt($ch, CURLOPT_TIMEOUT, 5);          // máx 5s total

Resultado

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:

Copiar
# 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.