Una persona en una terminal de Linux revisa una conexión HTTP manual hecha con Bash, junto a notas técnicas impresas sobre una mesa.

HTTP en Bash sin curl: cómo funciona

Aprende a hacer HTTP en Bash sin curl usando /dev/TCP, una técnica útil para troubleshooting, scripting mínimo y entornos donde no quieres dependencias extra. Ideal para perfiles técnicos en LatAm que necesitan soluciones rápidas y portables.

Si alguna vez te quedaste sin curl en un servidor, un contenedor mínimo o una imagen Alpine recortada al límite, seguro pensaste que ya no había mucho que hacer para probar una URL. Pero Bash tiene una salida bastante curiosa: puede abrir sockets TCP con /dev/TCP y, con un poco de cuidado, hablar HTTP sin instalar nada extra.

No es la forma más cómoda ni la que vas a usar todos los días. Aun así, sirve para debugging rápido, para entender mejor qué pasa entre tu shell y un servidor, y para salir del paso cuando solo tienes Bash, red y ganas de diagnosticar un problema sin meter paquetes nuevos.

Qué es /dev/TCP en Bash

/dev/TCP no es un archivo real del sistema. Es una redirección especial que Bash soporta en algunas compilaciones para abrir conexiones TCP desde la shell. En vez de leer o escribir a un archivo, Bash conecta la entrada o salida a un host y puerto remotos.

La sintaxis básica se parece a esto:

exec 3<>/dev/tcp/example.com/80

Con esa línea, Bash intenta abrir una conexión TCP a example.com en el puerto 80 y la asocia al descriptor de archivo 3. Después puedes escribir en ese descriptor y leer la respuesta. No hay magia HTTP todavía, solo una conexión TCP cruda.

Esto tiene una consecuencia práctica: tú construyes el protocolo a mano. Si quieres hacer una request HTTP, tienes que enviar una línea de request, headers correctos y el salto de línea final que el servidor espera. Si te equivocas en un solo detalle, el servidor puede responder mal o directamente cerrar la conexión.

Cuándo funciona y cuándo no

No todas las versiones de Bash tienen esta capacidad activada de la misma forma. En muchas distribuciones Linux funciona, pero no lo des por sentado en cualquier entorno. Según la documentación de Bash, /dev/tcp/host/port y /dev/udp/host/port son redirecciones especiales de Bash, no dispositivos del kernel. Puedes revisar la referencia oficial en GNU Bash manual: https://www.gnu.org/software/bash/manual/bash.html

En la práctica, esto suele estar disponible en shells Bash normales, pero no en sh genérico ni en shells más mínimas. Si estás dentro de un contenedor muy reducido, también puede fallar por falta de DNS, firewall o porque el binario no es Bash realmente.

Cómo hacer una request HTTP manual

Para hablar HTTP, necesitas enviar texto plano por el socket. Un request mínimo para HTTP/1.1 suele incluir al menos la línea de método, la ruta, el host y el cierre correcto de headers. En HTTP/1.1, el header Host es obligatorio en la mayoría de los servidores.

Un ejemplo funcional, usando GET / contra un servidor público, se vería así:

exec 3<>/dev/tcp/example.com/80
printf 'GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n' >&3
cat <&3
exec 3>&-
exec 3<&-

Ese bloque hace 4 cosas:

  1. Abre el socket TCP en el descriptor 3.
  2. Escribe un request HTTP válido.
  3. Lee la respuesta completa desde el mismo descriptor.
  4. Cierra el descriptor.

Si el servidor responde, vas a ver primero headers y luego el cuerpo HTML o JSON, según la ruta. Si el servicio usa HTTPS, este método no alcanza por sí solo, porque /dev/TCP solo abre TCP, no negocia TLS.

Qué devuelve realmente

Lo que recibes es la respuesta HTTP en texto plano. Eso incluye la línea de estado, headers y body. Por ejemplo, puedes ver algo como HTTP/1.1 200 OK, seguido de Content-Type, Content-Length y el contenido.

Si el servidor usa Connection: close, la lectura termina cuando el servidor cierra el socket. Si no, puedes quedarte esperando más datos de los necesarios. Por eso, en scripts mínimos, conviene pedir explícitamente el cierre de conexión cuando el servidor lo acepte.

Un ejemplo práctico con troubleshooting

Imagina que estás entrando por SSH a una VM de pruebas y quieres saber si un servicio web responde en el puerto 8080, pero no tienes curl, wget ni nc. Con Bash puedes validar conectividad, puerto abierto y respuesta HTTP en una sola pasada.

Primero, prueba la conexión TCP. Si esto falla, el problema no es HTTP sino red, firewall o servicio caído:

exec 3<>/dev/tcp/10.10.20.15/8080 && echo "puerto abierto" || echo "no conecta"
exec 3>&-
exec 3<&-

Si conecta, puedes mandar un request simple:

exec 3<>/dev/tcp/10.10.20.15/8080
printf 'GET /health HTTP/1.1\r\nHost: 10.10.20.15\r\nConnection: close\r\n\r\n' >&3
head -n 20 <&3
exec 3>&-
exec 3<&-

Ese patrón te sirve para revisar endpoints de salud, proxies internos, balanceadores o servicios de backend en redes privadas. También ayuda a distinguir entre tres problemas comunes: el puerto no responde, el servidor acepta TCP pero no entiende la request, o el endpoint responde con error de aplicación.

Errores típicos que puedes detectar

Hay señales bastante claras cuando algo falla:

  • Connection refused: el puerto no escucha o el servicio está caído.
  • No route to host: problema de red, ruteo o firewall.
  • Respuesta HTTP vacía: el servidor cerró la conexión o esperabas HTTPS en un puerto HTTP.
  • 400 Bad Request: la request está mal formada, normalmente por headers incompletos o saltos de línea incorrectos.

En troubleshooting real, esta técnica no reemplaza a curl, pero sí te da una prueba de bajo nivel útil cuando quieres aislar si el problema está en la red o en la app.

Limitaciones reales de Bash /dev/TCP

La primera limitación es obvia: no maneja HTTPS por sí solo. Si el sitio exige TLS, necesitas otra herramienta o un túnel que negocie TLS antes de hablar HTTP. Bash no va a cifrar nada en este modo.

La segunda limitación es que no parsea HTTP. Tú recibes bytes y texto, no una respuesta estructurada. Si quieres extraer un header o un JSON, tendrás que hacerlo con grep, awk, sed o jq si lo tienes instalado. Eso funciona para pruebas simples, pero no es una base elegante para automatizaciones complejas.

La tercera limitación es la portabilidad. Aunque Bash está en casi todos lados, /dev/TCP no es una garantía universal. En scripts de producción, si dependes de esto, conviene dejarlo como fallback y no como única ruta.

Comparación rápida con curl

TareaBash /dev/TCPcurl
Abrir conexión TCP
HTTPS/TLSNo
Seguir redirectsNo
Parsear headersManual
Dependencias extraNoSí, si no está instalado
Ideal para scripts mínimosSí, pero con dependencia

La tabla deja clara la idea: Bash sirve para lo mínimo necesario. curl sigue siendo la herramienta correcta cuando quieres robustez, opciones de protocolo y menos fricción.

Cómo escribir un script mínimo y útil

Si quieres convertir esta curiosidad en algo práctico, piensa en un script que haga una sola cosa: verificar que un endpoint responde y devolver un código de salida útil para CI, cron o un chequeo manual.

Un ejemplo sencillo podría ser este:

#!/usr/bin/env bash
set -euo pipefail

host="${1:-example.com}"
port="${2:-80}"
path="${3:-/}"

exec 3<>/dev/tcp/$host/$port
printf 'GET %s HTTP/1.1\r\nHost: %s\r\nConnection: close\r\n\r\n' "$path" "$host" >&3

status_line=$(head -n 1 <&3)
exec 3>&-
exec 3<&-

if [[ "$status_line" == *"200"* ]]; then
  echo "ok: $status_line"
  exit 0
fi

echo "fail: $status_line"
exit 1

Este script no intenta ser un cliente HTTP completo. Solo valida que hay respuesta y que el código se parece a un 200. Para un healthcheck rápido puede ser suficiente, siempre que controles bien el entorno y el endpoint.

Buenas prácticas para no romperte después

  1. Usa set -euo pipefail si vas a automatizar algo serio.
  2. Cierra siempre el descriptor con exec 3>&- y exec 3<&-.
  3. Envía Connection: close para simplificar la lectura.
  4. Evita endpoints HTTPS si no tienes una capa TLS adicional.
  5. Si el script va a vivir mucho tiempo, documenta que depende de Bash y no de sh.

Si quieres ver cómo Bash documenta estas redirecciones especiales, revisa la referencia oficial del manual: https://www.gnu.org/software/bash/manual/bash.html. Para entender mejor la forma correcta de construir requests HTTP, la especificación de HTTP Semantics en MDN también ayuda como referencia práctica: https://developer.mozilla.org/en-US/docs/Web/HTTP/Overview

Cuándo sí usarlo y cuándo no

Úsalo cuando necesitas una verificación rápida en un entorno mínimo, cuando estás depurando conectividad o cuando no puedes instalar paquetes por restricciones operativas. También sirve para enseñar cómo funciona una request HTTP por debajo de las herramientas cómodas que usamos todos los días.

No lo uses como reemplazo de curl en automatizaciones donde importan redirects, TLS, headers complejos, autenticación o parsing robusto. Tampoco lo metas en un script crítico si no puedes garantizar que Bash esté disponible con esa funcionalidad.

En equipos reales, esta técnica suele quedar como un truco de emergencia o como una herramienta de diagnóstico. Y eso ya la hace útil. Saber que Bash puede abrir un socket TCP te ayuda a pensar mejor el problema cuando todo lo demás falla.

Tabla resumen

PreguntaRespuesta corta
¿Qué hace /dev/TCP?Abre un socket TCP desde Bash.
¿Sirve para HTTPS?No, solo TCP sin TLS.
¿Necesita curl?No, esa es justo la idea.
¿Qué puerto usa HTTP?Normalmente 80.
¿Qué puerto usa HTTPS?Normalmente 443.
¿Para qué sirve más?Troubleshooting y scripts mínimos.

Preguntas frecuentes

¿Bash puede hacer HTTP de verdad sin curl?
Sí, pero en el sentido más básico: abre una conexión TCP y tú escribes el request HTTP a mano. Bash no entiende el protocolo por ti, solo te da el canal. Si el servidor responde, puedes leer la respuesta desde el mismo descriptor.
¿Esto funciona con HTTPS?
No directamente. `/dev/TCP` solo abre una conexión TCP, pero HTTPS necesita una negociación TLS antes de enviar HTTP. Para eso necesitas otra herramienta o una capa adicional que maneje cifrado.
¿En qué casos me sirve esta técnica?
Te sirve para troubleshooting, para validar puertos abiertos y para scripts mínimos en máquinas donde no quieres instalar dependencias. También es útil en contenedores reducidos o ambientes de rescate. No está pensada para reemplazar a curl en uso normal.
¿Qué pasa si el servidor usa HTTP/1.1 y no envío Host?
Muchos servidores responderán con error o con una respuesta inesperada. En HTTP/1.1 el header `Host` suele ser obligatorio, así que conviene enviarlo siempre. Si no lo haces, puedes terminar depurando un problema que en realidad es solo una request incompleta.
¿Cómo sé si el fallo es de red o de HTTP?
Si no puedes abrir el socket, el problema suele ser de red, firewall o servicio caído. Si el socket abre pero recibes un `400 Bad Request` o una respuesta rara, entonces el problema está en la request o en el servidor. Separar esas dos capas te ahorra tiempo.
¿Puedo usar esto en un script de producción?
Sí, pero solo como solución muy controlada y sabiendo exactamente qué dependencias tienes. Para casos críticos, curl sigue siendo más seguro y más expresivo. Si usas Bash, documenta bien la limitación y prueba el comportamiento en el mismo entorno donde va a correr.
¿Por qué no usar netcat en vez de Bash?
Porque a veces netcat tampoco está instalado. La gracia de `/dev/TCP` es que Bash ya viene en muchos sistemas, así que puedes salir del paso sin añadir paquetes. Aun así, si tienes `curl` o `nc`, normalmente serán mejores opciones.

Azirgo

¿Listo para construir tu Producto Digital?

Sitios web, apps móviles, software a medida y soluciones blockchain. Cuéntanos qué tienes en mente y armamos un plan claro contigo.

  • Cotización clara en 48 horas
  • Equipo en Ecuador, atención en español
  • Desde un MVP hasta un producto en producción