Una breve nota sobre quién escribe esto. Somos Geonode y vendemos proxies, por lo que «curl» es la herramienta que solemos recomendar a la gente para diagnosticar problemas. La verdad para un principiante: «curl» no es una herramienta de proxy y no necesitas un proxy para aprender a usarla. Todo lo que viene a continuación funciona con puntos finales públicos desde tu propia conexión, de forma gratuita. Los proxies cobran relevancia mucho más adelante, cuando realizas tantas solicitudes que el servidor de destino empieza a limitarte el ancho de banda, o cuando necesitas ver cómo se ve una página desde otro país. Ninguna de estas situaciones es un problema para un principiante. Primero, aprende a usar la herramienta.
Qué es curl y para qué sirve
curl es un programa de línea de comandos para transferir datos mediante URL. Su propio manual lo describe como compatible con «DICT, FILE, FTP, FTPS, GOPHER, GOPHERS, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, MQTT, MQTTS, POP3, POP3S, RTSP, SCP, SFTP, SMB, SMBS, SMTP, SMTPS, TELNET, TFTP, WS y WSS», aunque en la práctica casi todo el mundo lo utiliza para HTTP y HTTPS.
Para qué sirve:
- Llamar a una API desde un terminal o un script
- Comprobar si una URL funciona y qué devuelve
- Ver exactamente lo que devuelve un servidor, encabezados incluidos
- Descargar archivos
- Depuración: reproducir una solicitud fuera de tu aplicación para averiguar si el problema está en tu código o en el servidor
Lo que no es: un navegador. No ejecuta JavaScript, no muestra nada y no mantiene una sesión a menos que se le indique que lo haga. Una página que parece completa en un navegador puede devolver un esqueleto casi vacío a curl, y eso es lo esperado, no un error.
Tus primeras solicitudes
curl https://example.com
Esto realiza una solicitud GET y muestra el cuerpo de la respuesta en tu terminal. Si la salida es un texto extenso en formato HTML, significa que curl funciona correctamente.
Cuatro variantes que te resultarán útiles de inmediato:
Ver tanto los encabezados como el cuerpo con -i
, documentado en -i, --show-headers
: «Mostrar los encabezados de la respuesta en la salida».
curl -i https://example.com
Guarda en un archivo con -o
(el nombre que elijas) o -O
(el nombre remoto):
curl -o page.html https://example.com
curl -O https://example.com/file.zip
Sigue las redirecciones con -L
: «Sigue las redirecciones HTTP y repite las solicitudes con el método especificado originalmente». Sin este parámetro, curl se detiene en la primera redirección y te muestra la página de redirección en lugar del destino.
curl -L https://example.com
No mostrar mensajes, pero seguir informando de los errores con -sS
. -s
oculta el indicador de progreso, mientras que -S
mantiene los mensajes de error. Juntos son lo que necesitas en cualquier script.
curl -sS https://example.com
Si te quedas con una sola línea de este artículo, que sea esta:
curl -sSL https://example.com
Cómo interpretar la respuesta
Los principiantes suelen fijarse en el cuerpo de la respuesta cuando la respuesta está en los encabezados.
curl -i https://example.com
HTTP/2 200
content-type: text/html; charset=UTF-8
content-length: 1256
La primera línea indica el estado. 200
significa que la operación se ha realizado con éxito. 301
y 302
son redirecciones; hay que añadir -L
. 401
y 403
significan que no tienes permiso. 404
significa que no existe. 429
significa que vas demasiado rápido. 500
y valores superiores indican que el servidor tiene un problema.
content-type
te indica lo que has recibido realmente y aclara muchas dudas. Si has llamado a una API esperando JSON y ves text/html
, has recibido una página de error o una redirección de inicio de sesión, y el error de análisis que estás a punto de encontrar es un síntoma más que la causa.
Para obtener los encabezados sin el cuerpo:
curl -sS -o /dev/null -D - https://example.com
Esto realiza una solicitud GET normal, descarta el cuerpo y muestra los encabezados. Es más fiable que -I
, que envía una solicitud HEAD
y puede comportarse de forma diferente —una distinción que se trata en nuestra guía sobre solicitudes HEAD con curl.
Para obtener un resumen en lugar de los encabezados sin procesar, -w
muestra valores seleccionados:
curl -sS -o /dev/null -w 'status=%{response_code} time=%{time_total}s\n' https://example.com
Más información sobre cómo leer correctamente los encabezados en cómo mostrar los encabezados de respuesta con curl.
Envío de datos
La otra mitad del trabajo.
Una solicitud POST con datos de formulario:
curl -d "name=Ada&role=engineer" https://api.example.com/users
El uso de -d implica una solicitud POST y establece Content-Type: application/x-www-form-urlencoded.
Una solicitud POST con JSON —y este es el error más común entre los principiantes, ya que -d por sí solo no establece un tipo de contenido JSON:
curl -X POST https://api.example.com/users \
-H "Content-Type: application/json" \
-d '{"name":"Ada","role":"engineer"}'
Si te olvidas de ese encabezado, muchas API devuelven el error «415 Unsupported Media Type», lo cual resulta confuso hasta que sabes que se refiere a tu Content-Type en lugar de a tus datos. Hablamos de ese error concreto en ¿qué es un código de estado 415?.
Datos de un archivo, utilizando @ para indicar «lee este archivo»:
curl -X POST https://api.example.com/users \
-H "Content-Type: application/json" \
-d @payload.json
Una solicitud GET con parámetros de consulta formados por pares clave-valor, utilizando -G:
curl -G https://api.example.com/search -d "q=proxy" -d "limit=10"
Otros métodos con -X. Úsalo solo para métodos que no tengan una opción específica: PUT, DELETE, PATCH. Ten en cuenta la advertencia del manual de que -X «solo cambia la palabra concreta utilizada en la solicitud HTTP, no altera el comportamiento de curl», razón por la cual -X HEAD no funciona y existe -I.
Ver lo que realmente se ha transmitido
El hábito que distingue a quienes depuran rápidamente de quienes se limitan a adivinar.
curl -v https://example.com
El manual explica los prefijos: «>
: encabezado enviado por curl; <
: encabezado recibido por curl; }
: datos enviados por curl; {
: datos recibidos por curl; *
: información adicional proporcionada por curl».
Para ver solo lo que has enviado:
curl -v https://example.com 2>&1 | grep '^>'
Esto aclara toda una serie de confusiones, ya que el encabezado que estableces en el código no siempre es el que se ha transmitido. Las bibliotecas añaden valores por defecto, sobrescriben valores y reordenan elementos. Cuando un servidor «ignora» tu encabezado, comprueba primero si realmente lo has enviado.
La salida detallada se envía a stderr, por lo que es necesario utilizar «2>&1
» antes de la canalización —es algo deliberado, para que el cuerpo se mantenga limpio en stdout—.
Una advertencia que ofrece el manual y que vale la pena repetir: la salida detallada y de seguimiento «puede contener datos confidenciales, incluidos nombres de usuario, credenciales o contenido de datos secretos». Ocúlpalos antes de pegarlos en un ticket.
Las opciones que conviene recordar
Todo lo anterior se resume en un pequeño conjunto.
| Opción | Función |
|---|---|
-i | Mostrar los encabezados de la respuesta junto con el cuerpo |
-o file / -O | Guardar en un archivo con nombre / en el nombre remoto |
-L | Seguir las redirecciones |
-sS | Silencioso, pero sigue informando de los errores |
-H | Añadir un encabezado |
-d | Enviar datos (implica POST) |
-u | Autenticación básica |
-v | Mostrar el intercambio completo |
--fail | Tratar los errores HTTP como fallos |
-m / --connect-timeout | Límites de tiempo |
Las dos últimas son las que los principiantes se saltan y luego se arrepienten.
--fail es importante porque, por defecto, curl trata un 404 como una transferencia correcta: descarga la página de error y sale con un código de salida 0. En un script, eso significa que se guarda una página de error HTML con el nombre installer.dmg y se sigue adelante. --fail hace que los errores HTTP generen un código de salida distinto de cero y no produzcan ninguna salida.
Los tiempos de espera son importantes porque, por defecto, curl no tiene un límite de tiempo global. Una solicitud bloqueada deja el script colgado indefinidamente. --connect-timeout 5 -m 30 establece ese límite. Hay más información al respecto en cómo configurar un tiempo de espera con curl.
La línea que vale la pena incluir en todos los scripts:
curl --fail --silent --show-error --location --connect-timeout 5 --max-time 30 "$URL"
Un ejemplo práctico de principio a fin
Veamos cómo encajan todas las piezas en una tarea real: llamar a una API pública, comprobar que ha funcionado y gestionar el caso de error.
Paso uno: ver qué devuelve el punto final. Empieza por los encabezados, no por el cuerpo:
curl -sS -o /dev/null -D - https://api.github.com/repos/curl/curl
Obtendrás una línea de estado y los encabezados. Si el estado es 200
y content-type
indica JSON, te estás comunicando con lo correcto.
Paso dos: examina el cuerpo, formateado. El JSON sin procesar en una sola línea es ilegible, así que pásalo por jq
:
curl -sS https://api.github.com/repos/curl/curl | jq '{name, stargazers_count, language}'
Si jq
no está instalado, python3 -m json.tool
se encarga del formateo sin dependencias adicionales.
Paso tres: comprueba lo que has enviado. Si algo no funciona como debería, revisa la solicitud en lugar de hacer conjeturas:
curl -v https://api.github.com/repos/curl/curl 2>&1 | grep '^>'
Paso cuatro: haz que sea seguro para un script. Añade gestión de errores y límites de tiempo, y captura el estado por separado del cuerpo:
#!/usr/bin/env bash
set -euo pipefail
URL="https://api.github.com/repos/curl/curl"
BODY=$(mktemp)
STATUS=$(curl --silent --show-error --location \
--connect-timeout 5 --max-time 30 \
--write-out '%{response_code}' --output "$BODY" \
"$URL")
case "$STATUS" in
200) jq -r '.stargazers_count' < "$BODY" ;;
404) echo "not found" >&2; exit 1 ;;
429) echo "rate limited, retry after: $(date)" >&2; exit 1 ;;
*) echo "unexpected status $STATUS" >&2; head -c 200 "$BODY" >&2; exit 1 ;;
esac
rm -f "$BODY"
Hay tres aspectos que vale la pena aplicar en todo lo que escribas. --write-out '%{response_code}'
con --output
separa el estado del cuerpo para que puedas tomar decisiones en función de él. Mostrar los primeros 200 caracteres del cuerpo ante un estado inesperado convierte un misterio en un error legible. Y --connect-timeout
con --max-time
significa que el script finaliza incluso cuando la red no lo hace.
Paso cinco: respeta el límite de frecuencia. Las API públicas publican sus límites en los encabezados. Leerlos no cuesta nada y evita la forma más habitual de quedar bloqueado:
curl -sS -o /dev/null -D - https://api.github.com/repos/curl/curl | grep -i ratelimit
Errores comunes de principiantes
Olvidarse de -L. Se obtiene una respuesta corta que contiene un aviso de redirección y se concluye que la URL no funciona. No es así.
Olvidarse de --fail en los scripts. Un 404 se convierte en una página de error guardada y un código de salida de cero. Es un error silencioso, pero que sale caro más adelante.
Usar -d con JSON sin configurar Content-Type. Genera 415 o un error de análisis confuso en el servidor.
Comillas en el shell. Las comillas simples conservan todo al pie de la letra; las comillas dobles permiten que el shell expanda $ y las comillas invertidas. Para un cuerpo JSON que contenga comillas dobles, envuélvelo entre comillas simples. Si tus datos también contienen comillas simples, guárdalos en un archivo y utiliza -d @file.json.
Suponer que curl ve lo mismo que un navegador. curl no ejecuta JavaScript. Una respuesta casi vacía de una página que parece completa en un navegador significa que el contenido se representa en el lado del cliente, y que curl se está comportando correctamente.
Ignorar el código de estado. Un cuerpo que indique «error» con un estado 200 y otro que indique «error» con un estado 500 son problemas distintos. Lee ambos.
Introducir credenciales en la línea de comandos. Quedan registradas en el historial del shell y son visibles en la lista de procesos para otros usuarios de la máquina. Utiliza -u user y deja que curl te pida las credenciales, o que las lea de una variable de entorno.
Desactivar la verificación de certificados para que algo funcione. -k silencia una advertencia que te estaba indicando algo. Averigua primero de qué se trata.
Próximos pasos
Una vez que te sientas cómodo con los conceptos básicos, los siguientes pasos lógicos son:
Descargas correctas: reanudación de transferencias interrumpidas, descargas en paralelo y limitación de velocidad. Se trata en descargar un archivo con curl.
Tiempos de espera y reintentos, que es donde los scripts dejan de ser frágiles. Consulta cómo establecer un tiempo de espera con curl.
Lectura de encabezados como datos, con %{header_json}, que te ofrece una salida en JSON en lugar de texto que analizar.
curl frente a wget, ya que se solapan y son útiles para cosas diferentes; en curl vs wget se explica cuándo utilizar cada uno.
Proxies, para cuando los necesites: -x http://host:port redirige una solicitud a través de uno. Realmente útiles para comprobaciones geográficas y para distribuir el volumen; realmente innecesarios para aprender o para un uso moderado.
El manual. man curl es extenso y constituye la fuente de referencia. Leer la entrada correspondiente a una opción que ya utilizas es una forma fiable de descubrir la opción que realmente buscabas.
Preguntas frecuentes
¿Para qué se utiliza curl?
Para transferir datos a través de URL desde la línea de comandos o un script: llamar a API, comprobar qué devuelve un servidor, descargar archivos y reproducir una solicitud fuera de una aplicación para aislar un problema. Admite muchos protocolos, pero se utiliza principalmente para HTTP y HTTPS.
¿Cómo realizo una solicitud GET con curl?
curl https://example.com. GET es el valor por defecto, por lo que no se necesita ningún parámetro. Añade -L para seguir las redirecciones y -i para ver los encabezados de la respuesta junto con el cuerpo.
¿Cómo envío JSON con curl?
curl -X POST -H "Content-Type: application/json" -d '{"key":"value"}' URL. El encabezado es esencial: -d por sí solo envía un tipo de contenido codificado como formulario, y las API que esperan JSON suelen rechazarlo con un código de error 415.
¿Por qué curl no devuelve nada?
Hay varias posibilidades: el cuerpo de la respuesta está realmente vacío; has seguido una redirección que no habías previsto (añade -L); el contenido lo genera JavaScript, que curl no ejecuta; o la solicitud ha fallado y no has visto el error porque has utilizado -s sin -S. Ejecútalo con -i para ver el código de estado.
¿Cuál es la diferencia entre -o y -O en curl?
-o filename guarda el archivo con el nombre que elijas. -O guarda el archivo con el nombre que aparece en la URL, descartando la ruta. Utiliza -o cuando la URL no contenga un nombre de archivo útil o cuando necesites un nombre específico.
¿Cómo puedo ver la solicitud que envía curl?
curl -v URL y busca las líneas que empiezan por >. La salida detallada se envía a stderr, así que añade 2>&1 antes de la canalización. Esta es la forma más rápida de confirmar que un encabezado que has configurado se ha enviado realmente.
¿Sigue curl las redirecciones de forma predeterminada?
No. Añade -L. Esta es la razón más habitual por la que el comando curl de un principiante devuelve una respuesta breve e inesperada: estás viendo la redirección, no el destino.
¿Necesito un proxy para usar curl?
No. curl funciona perfectamente con puntos finales públicos desde tu propia conexión. Los proxies solo cobran relevancia cuando realizas tantas solicitudes que te limitan el ancho de banda, o cuando necesitas ver qué ofrece un sitio web en otro país. Ninguna de estas razones justifica comprar nada mientras estás aprendiendo.
Conclusión
curl tiene una cantidad abrumadora de opciones y un núcleo útil muy reducido. -i para ver los encabezados, -L para seguir redirecciones, -o para guardar, -H para añadir encabezados, -d para enviar datos, -u para la autenticación, -v para ver qué ha pasado y --fail, además de un tiempo de espera para cualquier proceso que se ejecute sin supervisión. Ese es todo el conjunto de funciones que la mayoría de la gente necesita.
Los dos hábitos que importan más que cualquier opción: lee el código de estado y el Content-Type antes de leer el cuerpo del mensaje, porque suelen indicar el problema de forma directa; y utiliza -v para comprobar lo que realmente has enviado en lugar de lo que pretendías enviar, porque la diferencia entre ambos es donde se esconden una cantidad sorprendente de errores.
Todo lo demás lo encontrarás en el manual, que es extenso, fidedigno y al que merece la pena echar un vistazo cada vez que te encuentres escribiendo una solución alternativa. La opción que buscas suele estar ahí.
