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 curl instalado (curl --version para confirmar; instala con sudo apt install curl o sudo 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: jq instalado 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ísticacurlwget
Caso de uso principalLlamadas a API, depuración HTTP, scriptingDescargas recursivas, duplicación de sitios
Soporte de API RESTCompleto (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 redireccionesRequiere la opción -LLas sigue por defecto
Reanudar descargas-C -Opción -c
Descarga recursivaNo soportadaOpción --recursive
Salida a stdoutPor defectoRequiere -O -
Scripting / pipesExcelente (stdout por defecto)Menos natural
Protocolos soportados30+ (FTP, SFTP, SMTP, IMAP…)HTTP, HTTPS, FTP
Visualización de progreso-# o --progress-barSe 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

  • curl es el cliente HTTP principal de Linux para pruebas de APIs REST, transferencia de archivos y depuración de red
  • Usa -X para establecer el método HTTP (POST, PUT, PATCH, DELETE), -H para cabeceras, -d para el cuerpo de la petición
  • Autenticación: -H "Authorization: Bearer TOKEN" para autenticación por token, -u usuario:contraseña para Basic Auth
  • Descarga archivos con -O (nombre original) o -o nombre; usa -L para seguir redirecciones
  • El modo verbose -v muestra 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-timeout y --max-time para 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-raw cuando los datos del cuerpo contienen caracteres @ literales para evitar confusión con lectura de archivos
  • Redirige a jq para salida JSON formateada: curl -s https://api.example.com/data | jq .

Artículos Relacionados