TIL: You can make HTTP requests without curl using Bash /dev/TCP

Learn how to make raw HTTP requests and debug network connectivity inside minimal Docker containers using Bash's built-in /dev/tcp redirection, without needing curl or wget.
I needed to check that one container could reach another over an internal Docker network: a plain GET /health against a service on a shared network. The obvious move is curl http://service:8642/health. But this app image was stripped right down, with no curl or wget and nothing else around that I could use to open a socket.
As it turns out, bash can speak HTTP by itself. bash can open a TCP socket, and you can write a small HTTP request to it by hand. Opening a connection to a host and port and writing the request needs nothing beyond the shell that’s already there:
exec 3<>/dev/tcp/service/8642
printf 'GET /health HTTP/1.1\r\nHost: service\r\nConnection: close\r\n\r\n' >&3
cat <&3
service here is just the hostname of whatever you’re talking to. It has to resolve and be reachable from wherever you run this, so it needs to be set up first: a container or service name on a Docker network you’ve configured, or any DNS name that resolves. Swap in your own host and port.
That prints the whole response: the status line, the headers, the blank line, and the body. To add a header, such as an Authorization: Bearer token, put another \r\n-terminated line before the blank line that ends the request:
exec 3<>/dev/tcp/service/8642
printf 'GET /v1/models HTTP/1.1\r\nHost: service\r\nAuthorization: Bearer %s\r\nConnection: close\r\n\r\n' "$API_KEY" >&3
cat <&3
What caught me out the first time is that /dev/tcp isn’t a real device file. There’s no such path on disk; ls /dev/tcp finds nothing, and cat /dev/tcp/... from another shell just errors. It’s a redirection that bash handles internally. From the Bash manual:
/dev/tcp/host/port– If host is a valid hostname or Internet address, and port is an integer port number or service name, bash attempts to open the corresponding TCP socket.
The names were picked because no real Unix has a /dev/tcp or /dev/udp hierarchy, so there’s nothing to collide with. Bash does the DNS lookup and the connect(2) for you, and exec 3<> hands the socket a file descriptor (3) you read from and write to like any other.
A few things worth knowing:
- This is not a real HTTP client. It does not parse HTTP properly, handle redirects, chunked responses, compression, retries, TLS, or any of the other things
curlquietly does for you. It’s a quick connectivity and debugging trick. - The
Connection: closeheader matters. Without it the server keeps the connection open after it responds, which is the HTTP/1.1 default, andcat <&3then waits forever for bytes that never arrive. Asking the server to close meanscatreaches EOF and returns. Wrapping the call intimeout 6 bash -c '...'covers you either way. - There’s no TLS.
/dev/tcpopens a raw socket, so this only works for plaintext HTTP. Forhttpsyou’d needopenssl s_client, and by then you may as well have the proper tools. - This is a
bashfeature, not POSIX.dash(Debian’s/bin/sh) andzshdon’t have it, so a#!/bin/shscript can’t use it. Callbashdirectly. - It’s a compile-time option, switched on when
bashis built with--enable-net-redirections. Most mainstream builds enable it, and it worked without any fuss in the Debian-based image I was in, but Debian shipped it disabled for years, so on an old or very minimal system it’s worth checking first.
For day-to-day work curl is still the right tool. But inside a deliberately small container where you can’t install anything, this gets a quick check done without adding a package.
Source: Hacker News













