>_ josetilabs.com
← blog

15 out 2026 · ferramentas, linux, api · 6 min

O que dá para medir com curl em uma chamada

Uma requisição https para um host público levou 63,3 ms na média de dez chamadas. O handshake TLS sozinho tomou 41,5 ms disso, mais que a metade. Nenhuma dessas cinco fases apareceu no -w quando eu pedi só o total. Separar a chamada por fase é o que transforma 63 ms em uma decisão.

O -w é o instrumento

O -w escreve uma linha de medição no lugar de falar com o servidor. Cada %{...} é uma variável do próprio curl. As cinco fases vêm de uma chamada só:

bash
curl -sS -o /dev/null \
  -w 'dns=%{time_namelookup} tcp=%{time_connect} tls=%{time_appconnect}\nttfb=%{time_starttransfer} total=%{time_total}\n' \
  https://exemplo.org

O -o /dev/null joga o corpo fora, o -sS cala o progresso mas deixa o erro aparecer. As variáveis saem acumuladas, como um cronômetro que não zera: time_connect já contém o DNS. Para ver a parcela de cada uma, subtraia.

Grafico de barras com as cinco fases acumuladas de uma requisicao https medidas em 10 chamadas: dns 3,0 ms, conexao tcp 11,7 ms, tls 41,5 ms, primeira resposta 63,2 ms e total 63,3 ms
As barras são acumuladas, do jeito que o curl imprime. O degrau mais alto é o TLS.

O dns custou 3,0 ms, a conexão tcp mais 8,7 ms, o tls mais 29,8 ms, e o servidor mais 21,7 ms. Se a sua API está lenta, esse degrau final é o número que interessa. Se ela responde rápido e a lentidão aparece do outro lado da rede, o handshake é o suspeito.

O mesmo raciocínio de decompor em fases, mas medindo em vez de adivinhar, está no post dez imports custam 100 ms na partida.

O artefato: seis receitas em um script

O medir-api.sh abaixo roda as seis medições contra qualquer API que responda nas rotas /saude, /eco, /vai e /quebra. Ele não instala nada, não escreve arquivo e sai com código 0 mesmo quando o curl não está presente.

bash
#!/usr/bin/env bash
# medir-api.sh: as receitas de curl que valem para qualquer API.
#
#   BASE=http://127.0.0.1:8099 bash medir-api.sh
#
# O script roda seis medicoes: tempo por fase, cabecalho de resposta,
# HEAD sem corpo, redirect com e sem -L, POST em json e erro com -f.
# Ele nao instala nada e nao escreve arquivo.

BASE="${BASE:-https://example.org}"
SAUDE="${SAUDE:-/saude}"
ECO="${ECO:-/eco}"
VAI="${VAI:-/vai}"
ERRO="${ERRO:-/quebra}"

# As cinco fases do curl, acumuladas como ele imprime.
FMT='dns=%{time_namelookup} tcp=%{time_connect} tls=%{time_appconnect} ttfb=%{time_starttransfer} total=%{time_total}\n'

precisa_de_curl() {
  if ! command -v curl >/dev/null 2>&1; then
    echo "curl nao esta instalado neste ambiente."
    echo "em Debian ou Ubuntu: apt-get install -y curl"
    echo "em Alpine: apk add curl"
    return 1
  fi
  return 0
}

precisa_de_curl || exit 0

echo "== 1. tempo por fase =="
curl -sS -o /dev/null -w "$FMT" "$BASE$SAUDE"

echo
echo "== 2. cabecalho de resposta, sem o corpo =="
curl -sS -D - -o /dev/null "$BASE$SAUDE"

echo
echo "== 3. HEAD: so o tamanho declarado =="
curl -sS -I "$BASE$SAUDE" | grep -i 'content-length' | head -1

echo
echo "== 4. redirect: sem -L e com -L =="
curl -sS -o /dev/null -w 'code=%{http_code} redirects=%{num_redirects} total=%{time_total}\n' "$BASE$VAI"
curl -sS -L -o /dev/null -w 'code=%{http_code} redirects=%{num_redirects} total=%{time_total}\n' "$BASE$VAI"

echo
echo "== 5. POST em json, e quantos bytes sairam =="
curl -sS -X POST "$BASE$ECO" \
  -H 'Content-Type: application/json' \
  -d '{"nome":"joseti","tentativa":1}' \
  -w '\nenviados=%{size_upload} total=%{time_total}\n'

echo
echo "== 6. erro vira codigo de saida =="
curl -sS -o /dev/null -w 'code=%{http_code}\n' "$BASE$ERRO"
curl -sS -f -o /dev/null "$BASE$ERRO"
echo "com -f o curl saiu com codigo $?"

Rodando contra um servidor de teste que responde 200, devolve 302 numa rota e 500 numa rota de erro, a saída foi de 0,911 ms na primeira medição, cabeçalho com Content-Length: 50, o mesmo 50 no HEAD, redirects=0 sem -L contra redirects=1 com ele, 31 bytes enviados no POST e código 22 no final.

POST em json, com o arquivo quando o corpo é grande

O -H 'Content-Type: application/json' não é decoração: sem ele o curl manda application/x-www-form-urlencoded e a API rejeita o corpo.

bash
curl -sS -X POST https://exemplo.org/api/eco \
  -H 'Content-Type: application/json' \
  -d '{"nome":"joseti","tentativa":1}'

Para corpo grande ou com acento, use --data-binary @arquivo. Com -d @arquivo, o curl tira as quebras de linha e os retornos de carro do arquivo antes de enviar, e o Content-Length sai errado. Com --data-binary, nenhum byte muda.

bash
curl -sS -X POST https://exemplo.org/api/eco \
  -H 'Content-Type: application/json' \
  --data-binary @carga.json

Para confirmar quantos bytes realmente saíram, junte com -w 'enviados=%{size_upload}\n'. Foi assim que conferi 2.258 bytes num arquivo de exemplo.

Ver o cabeçalho, e só o cabeçalho

-D - despeja os cabeçalhos da resposta em stdout e -o /dev/null segura o corpo. Com -sS e -D -, a saída cabe numa tela:

bash
curl -sS -D - -o /dev/null https://exemplo.org/api/saude

Para só o tamanho declarado, curl -sI url | grep -i content-length faz o pedido HEAD, que o servidor responde sem enviar corpo nenhum. É a forma barata de checar se o que está no CDN é o que você espera.

Redirect: o -L dobra o tempo e some do número

Um 302 seguido, medido contra servidor local, deu 0,70 ms sem -L e 1,22 ms com -L. Com o -L, o curl faz a segunda chamada, mas o total impresso é o da última. Quem compara com o número de antes está comparando coisas diferentes.

bash
curl -sS -o /dev/null -w 'code=%{http_code} redirects=%{num_redirects}\n' https://exemplo.org/vai
curl -sS -L -o /dev/null -w 'code=%{http_code} redirects=%{num_redirects} total=%{time_total}\n' https://exemplo.org/vai

Sempre imprima %{num_redirects} junto do code. Um 301 que virou 302 é o tipo de coisa que passa semanas sem ninguém olhar.

Terminal com a chamada de GET mostrando cabecalhos, a resposta 500 com o codigo de saida 22 do curl -f e a chamada de POST em json devolvida pelo servidor de teste
As três receitas juntas: cabeçalho, erro com código de saída e POST devolvido.

Deixar o erro virar código de saída

Sem -f, um 500 vem com exit 0 e parece sucesso. Com -f, o curl sai com 22 e o set -e do script para. Foi o que aconteceu no teste contra a rota de erro.

bash
curl -sS -f https://exemplo.org/api/saude || echo "a chamada falhou"

Tabela

Receita O que resolve
-w ver o tempo por fase
-D - ver cabeçalho de resposta
-I só cabeçalho, sem corpo
-L seguir redirect
-f erro HTTP vira saída 22
--data-binary @arq corpo grande sem perder byte

Se der errado

-w não mostra nada além do total. Causa: você escreveu %{time_*} com barra invertida fora do par de aspas. Correção: -w só imprime se a string estiver entre aspas duplas.

A resposta vem com quebras de linha e fica ilegível no shell. Causa: o corpo veio junto. Correção: -o /dev/null quando você só quer o -w.

curl: (60) SSL certificate problem. Causa: cadeia incompleta no sistema ou proxy no meio. Correção: -k só para testar, nunca em script que vai para produção.

FAQ

-s esconde o erro também? Esconde. -sS cala o progresso e mantém o erro no stderr, e é esse que você quer.

Como salvo a resposta num arquivo? -o saida.json, e com -w junto o -o recebe só o corpo. Para ver e gravar ao mesmo tempo, use --dump-header.

--compressed faz diferença? Faz, quando a resposta vem em gzip. Ele pede a descompressão automática e o %{size_download} passa a contar os bytes já descompactados.

Dá para usar isso dentro de script sem instalar nada? O curl já vem na maioria das imagens Debian e Ubuntu. A imagem python:3-slim também tem, e a alpine precisa de apk add curl.

E httpie, wget ou xh? Wget resolve download e arquivo grande; httpie tem sintaxe mais bonita para humano. O curl ganha quando o script precisa do tempo por fase, do código de saída e do cabeçalho na mesma chamada.

Fontes

manual do curl e o capítulo de HTTP do guia do projeto, conferidos na versão 8.23. Cada variável do -w está documentada com nome na própria página, vale abrir antes de inventar uma nova. Para o outro lado da latência, quando o problema é o tempo do modelo e não o da rede, o post avaliando se o modelo piorou mostra o método.

Leia também

// Por e-mail

Os textos publicados aqui também vão por e-mail. O envio ainda não começou: quem deixar o endereço agora entra na primeira lista.

Guardo só o seu e-mail, a data e o registro desta autorização. Sem cookies, sem rastreador e nada compartilhado com terceiros. Detalhes na política de privacidade.