El comando curl que los usuarios de Linux utilizan a diario es mucho más que una herramienta de descarga: es un cliente HTTP completo capaz de probar APIs REST, automatizar flujos de trabajo, depurar problemas de red y transferir datos sobre docenas de protocolos. Ya sea que estés solucionando un webhook roto, escribiendo una integración con una API o inspeccionando certificados TLS, dominar curl es una de las habilidades de mayor impacto que puede tener un sysadmin o desarrollador. Esta guía recorre cada caso de uso práctico, desde GETs simples hasta pruebas completas de APIs REST con CRUD, con ejemplos del mundo real a lo largo de todo el texto.
Requisitos Previos
- Un sistema Linux con
curlinstalado (curl --versionpara confirmar; instala consudo apt install curlosudo dnf install curl) - Familiaridad básica con la terminal y los conceptos HTTP (métodos de petición, cabeceras, códigos de estado)
- Un endpoint de API para probar — los ejemplos usan
https://jsonplaceholder.typicode.com, una API simulada pública y gratuita - Opcional:
jqinstalado para imprimir respuestas JSON con formato (sudo apt install jq)
Hacer Peticiones HTTP: GET, POST, PUT, DELETE
curl usa GET por defecto, así que el comando más simple posible es simplemente una URL:
curl https://jsonplaceholder.typicode.com/posts/1
Agrega -s (silencioso) para suprimir el medidor de progreso en scripts, y redirige a jq para una salida legible:
curl -s https://jsonplaceholder.typicode.com/posts/1 | jq .
POST — crear un recurso:
curl -s -X POST \
-H "Content-Type: application/json" \
-d '{"title":"Mi Post","body":"Hola mundo","userId":1}' \
https://jsonplaceholder.typicode.com/posts | jq .
La opción -X POST establece el método. -H agrega una cabecera. -d envía el cuerpo de la petición. Para envíos de formularios en lugar de JSON, omite la cabecera Content-Type o usa -d "campo=valor&otro=dato".
PUT — actualizar un recurso:
curl -s -X PUT \
-H "Content-Type: application/json" \
-d '{"title":"Título Actualizado","body":"Nuevo cuerpo","userId":1}' \
https://jsonplaceholder.typicode.com/posts/1 | jq .
PATCH — actualización parcial:
curl -s -X PATCH \
-H "Content-Type: application/json" \
-d '{"title":"Solo el título cambió"}' \
https://jsonplaceholder.typicode.com/posts/1 | jq .
DELETE — eliminar un recurso:
curl -s -X DELETE https://jsonplaceholder.typicode.com/posts/1
echo "Estado HTTP: $?"
Para capturar el código de estado HTTP de forma explícita:
curl -s -o /dev/null -w "%{http_code}" -X DELETE \
https://jsonplaceholder.typicode.com/posts/1
La opción -w (write-out) con %{http_code} muestra solo el código de estado numérico, ideal para verificar éxito o fallo en scripts de shell.
Autenticación: Cabeceras, Bearer Tokens y Basic Auth
La mayoría de las APIs de producción requieren autenticación. curl gestiona todos los esquemas comunes.
Bearer token (OAuth 2.0 / JWT):
curl -s -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
https://api.example.com/protected-resource | jq .
Autenticación HTTP Basic — dos formas equivalentes:
# Forma 1: opción -u (curl agrega la cabecera Authorization automáticamente)
curl -s -u miusuario:micontraseñasecreta https://api.example.com/data
# Forma 2: cabecera explícita (útil al escribir scripts con variables)
curl -s -H "Authorization: Basic $(echo -n miusuario:micontraseñasecreta | base64)" \
https://api.example.com/data
Clave API como parámetro de consulta:
curl -s "https://api.example.com/data?api_key=TU_CLAVE_AQUI" | jq .
Clave API como cabecera personalizada (común con servicios como OpenAI o Stripe):
curl -s -H "X-API-Key: TU_CLAVE_AQUI" https://api.example.com/endpoint | jq .
Almacenar credenciales en un archivo netrc mantiene los secretos fuera del historial del shell:
# ~/.netrc
machine api.example.com
login miusuario
password micontraseñasecreta
curl -s --netrc https://api.example.com/data
Descarga y Subida de Archivos
Descarga básica — guardar con el nombre de archivo original:
curl -O https://releases.ubuntu.com/24.04/ubuntu-24.04-desktop-amd64.iso
Descargar a una ruta específica:
curl -o /tmp/ubuntu.iso https://releases.ubuntu.com/24.04/ubuntu-24.04-desktop-amd64.iso
Seguir redirecciones (muchas URLs de descarga redirigen):
curl -L -O https://github.com/cli/cli/releases/latest/download/gh_linux_amd64.tar.gz
Reanudar una descarga interrumpida:
curl -C - -O https://example.com/large-file.tar.gz
Descargar con barra de progreso (útil en terminales interactivas):
curl --progress-bar -O https://example.com/large-file.tar.gz
Subir un archivo con multipart/form-data (simulando una entrada de archivo del navegador):
curl -s -X POST \
-F "file=@/ruta/al/documento.pdf" \
-F "description=Mi documento" \
https://api.example.com/upload | jq .
Subir binario sin procesar con PUT:
curl -s -X PUT \
-H "Content-Type: application/octet-stream" \
--data-binary @/ruta/a/imagen.png \
https://api.example.com/files/imagen.png
Depuración Verbose con -v y —trace
La opción -v es tu mejor herramienta de depuración. Imprime el handshake TLS, las cabeceras de la petición, las cabeceras de la respuesta y la línea de estado:
curl -v https://jsonplaceholder.typicode.com/posts/1
Salida de ejemplo (truncada):
* Connected to jsonplaceholder.typicode.com (104.21.x.x) port 443
* TLSv1.3, TLS handshake
> GET /posts/1 HTTP/2
> Host: jsonplaceholder.typicode.com
> User-Agent: curl/8.5.0
> Accept: */*
>
< HTTP/2 200
< content-type: application/json; charset=utf-8
< cache-control: max-age=43200
<
{ ... cuerpo JSON ... }
Las líneas que comienzan con > son enviadas por curl; las que comienzan con < son recibidas del servidor. Esto es invaluable para verificar que tus cabeceras se están enviando correctamente.
Para obtener aún más detalle, usa --trace para volcar los bytes sin procesar:
curl --trace /tmp/curl-trace.txt https://api.example.com/endpoint
Verificar solo cabeceras sin descargar el cuerpo:
curl -I https://example.com
-I envía una petición HEAD. Para enviar HEAD especificando aun así un método personalizado:
curl -s -X HEAD -I https://example.com
Inspeccionar la información del certificado TLS:
curl -v --connect-to :: https://example.com 2>&1 | grep -A5 "Server certificate"
O con --cert-status en compilaciones compatibles.
curl vs wget: Elegir la Herramienta Correcta
Tanto curl como wget descargan archivos, pero sirven para propósitos principales distintos:
| Característica | curl | wget |
|---|---|---|
| Caso de uso principal | Llamadas a API, depuración HTTP, scripting | Descargas recursivas, duplicación de sitios |
| Soporte de API REST | Completo (GET/POST/PUT/DELETE/PATCH) | Solo GET por defecto |
| Cuerpo de petición JSON | -d '{"key":"val"}' | No soportado nativamente |
| Cabeceras personalizadas | -H "Cabecera: Valor" | --header="Cabecera: Valor" |
| Seguir redirecciones | Requiere la opción -L | Las sigue por defecto |
| Reanudar descargas | -C - | Opción -c |
| Descarga recursiva | No soportada | Opción --recursive |
| Salida a stdout | Por defecto | Requiere -O - |
| Scripting / pipes | Excelente (stdout por defecto) | Menos natural |
| Protocolos soportados | 30+ (FTP, SFTP, SMTP, IMAP…) | HTTP, HTTPS, FTP |
| Visualización de progreso | -# o --progress-bar | Se muestra por defecto |
Regla general: Usa curl para trabajo con APIs, depuración y pipelines de scripting. Usa wget cuando necesites duplicar o descargar recursivamente un sitio web, o al reanudar descargas con una invocación más simple.
Escenario del Mundo Real: Probar una API REST de Extremo a Extremo
Tienes un servidor de producción ejecutando un nuevo microservicio de gestión de usuarios. Antes de desplegarlo, quieres validar el ciclo de vida completo CRUD desde la línea de comandos, capturando códigos de estado para la integración con CI/CD.
#!/bin/bash
set -euo pipefail
BASE="https://api.example.com/v1"
TOKEN="eyJhbGciOiJIUzI1NiIs..."
echo "=== Probando API de Gestión de Usuarios ==="
# 1. Verificación de salud
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$BASE/health")
echo "Verificación de salud: $STATUS"
[[ "$STATUS" == "200" ]] || { echo "FALLO: API no disponible"; exit 1; }
# 2. Crear un nuevo usuario
RESPONSE=$(curl -s -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"username":"usuarioprueba","email":"prueba@example.com","role":"viewer"}' \
"$BASE/users")
USER_ID=$(echo "$RESPONSE" | jq -r '.id')
echo "ID de usuario creado: $USER_ID"
# 3. Obtener el usuario
curl -s -H "Authorization: Bearer $TOKEN" \
"$BASE/users/$USER_ID" | jq '.username'
# 4. Actualizar el rol del usuario
STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
-X PATCH \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"role":"editor"}' \
"$BASE/users/$USER_ID")
echo "Estado PATCH: $STATUS"
[[ "$STATUS" == "200" ]] || echo "ADVERTENCIA: se esperaba 200, se obtuvo $STATUS"
# 5. Listar todos los usuarios y contar
COUNT=$(curl -s -H "Authorization: Bearer $TOKEN" \
"$BASE/users" | jq 'length')
echo "Total de usuarios: $COUNT"
# 6. Eliminar usuario de prueba
STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
-X DELETE \
-H "Authorization: Bearer $TOKEN" \
"$BASE/users/$USER_ID")
echo "Estado DELETE: $STATUS"
[[ "$STATUS" == "204" ]] || echo "ADVERTENCIA: se esperaba 204, se obtuvo $STATUS"
echo "=== Todas las pruebas pasaron ==="
Este script valida el contrato completo de la API y termina con un código distinto de cero si algo falla, listo para integrarse en un pipeline de CI/CD.
Errores Comunes y Casos Especiales
Errores de certificado SSL en servidores internos o de desarrollo: Usa -k o --insecure para omitir la verificación, pero solo en desarrollo:
curl -k https://servidor-dev-interno.local/api
Comillas simples vs comillas dobles en shells: Dentro de comillas dobles, $ y las comillas inversas se expanden. Usa comillas simples alrededor de los cuerpos JSON para evitar sorpresas:
# Seguro: las comillas simples evitan la expansión de variables dentro del JSON
curl -d '{"userId":1}' https://api.example.com/items
# Riesgoso: $HOME se expande dentro de comillas dobles
curl -d "{\"userId\":$USER_ID}" https://api.example.com/items # OK si $USER_ID es intencional
Enviar un @ literal en datos POST: curl interpreta -d @archivo como “leer cuerpo del archivo”. Para enviar un @ literal, usa --data-raw:
curl --data-raw '{"email":"usuario@example.com"}' https://api.example.com/subscribe
Límite de velocidad y reintentos: Agrega --retry y --retry-delay para scripts resilientes:
curl --retry 5 --retry-delay 2 --retry-all-errors \
-s https://api.example.com/endpoint | jq .
Control de tiempo de espera: Evita que los scripts se queden colgados indefinidamente:
curl --connect-timeout 10 --max-time 30 https://api.example.com/endpoint-lento
--connect-timeout limita la fase de conexión; --max-time limita el tiempo total de transferencia.
Cookies: Envía y persiste cookies para APIs basadas en sesiones:
# Guardar cookies en un archivo después del inicio de sesión
curl -s -c /tmp/cookies.txt -X POST \
-d "username=admin&password=secreto" \
https://example.com/login
# Reutilizar cookies guardadas para peticiones autenticadas
curl -s -b /tmp/cookies.txt https://example.com/dashboard
Solución de Problemas
“Could not resolve host”: Fallo de DNS. Verifica /etc/resolv.conf o prueba con curl --dns-servers 8.8.8.8 https://example.com.
“SSL: no alternative certificate subject name matches”: El certificado del servidor no coincide con el nombre de host. Usa -v para ver qué dice el certificado; el nombre de host en tu URL puede ser incorrecto.
“Empty reply from server”: El servidor cerró la conexión sin enviar cabeceras. Prueba con -v para ver si el handshake TLS fue exitoso; puede indicar un desajuste de protocolo (la opción --http1.1 puede ayudar).
El cuerpo de la respuesta está vacío pero el estado es 200: Algunos endpoints devuelven 204 No Content en caso de éxito (especialmente DELETE). Usa -v para confirmar la línea de estado.
Respuesta grande truncada en la terminal: Redirige a less o a un archivo:
curl -s https://api.example.com/conjunto-datos-grande | jq . | less
curl -s https://api.example.com/conjunto-datos-grande > /tmp/respuesta.json
Verificar qué versión de curl tienes y qué protocolos soporta:
curl --version
Busca Protocols: en la salida — esto muestra la disponibilidad de FTP, SFTP, HTTP/2, HTTP/3 dependiendo de tu compilación.
Resumen
curles el cliente HTTP principal de Linux para pruebas de APIs REST, transferencia de archivos y depuración de red- Usa
-Xpara establecer el método HTTP (POST, PUT, PATCH, DELETE),-Hpara cabeceras,-dpara el cuerpo de la petición - Autenticación:
-H "Authorization: Bearer TOKEN"para autenticación por token,-u usuario:contraseñapara Basic Auth - Descarga archivos con
-O(nombre original) o-o nombre; usa-Lpara seguir redirecciones - El modo verbose
-vmuestra las cabeceras completas de petición/respuesta y los detalles TLS, esencial para depuración -w "%{http_code}"extrae el código de estado para scripting y validación en CI/CD- Usa
--retry,--connect-timeouty--max-timepara hacer scripts resilientes en producción - Prefiere curl sobre wget para trabajo con APIs y scripting; usa wget para descargas recursivas de sitios
- Usa siempre
--data-rawcuando los datos del cuerpo contienen caracteres@literales para evitar confusión con lectura de archivos - Redirige a
jqpara salida JSON formateada:curl -s https://api.example.com/data | jq .