TIL: You can make HTTP requests without curl using Bash /dev/TCP
TIL: Performing HTTP Requests via Bash /dev/tcp
The Scenario: A Stripped-Down Environment
Imagine you are troubleshooting a containerized environment. You need to verify that one service can communicate with another over an internal Docker network—specifically, a simple GET /health check.
However, the image is extremely minimal. You find that and curl are both missing, and there are no other utilities available to open a network socket.wget
The Solution: It turns out that Bash is capable of speaking HTTP on its own. By leveraging its ability to open TCP sockets, you can manually construct and send an HTTP request.
🛠️ Implementation
To establish a connection and transmit a request, you only need the shell. Here is the basic workflow:
# 1. Open a TCP socket to the service on port 8642 and assign it to file descriptor 3
exec 3 /dev/tcp/service/8642
# 2. Send the HTTP request formatted with CRLF (\r\n)
printf 'GET /health HTTP/1.1\r\nHost: service\r\nConnection: close\r\n\r\n' > 3
# 3. Read the response from the file descriptor
cat 3
Note: In the example above,
servicerepresents the hostname. This must be a resolvable DNS name or a configured Docker service name.
Adding Custom Headers
If you need to include an Authorization token (e.g., a Bearer token), simply insert another line before the final blank line that signals the end of 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
🔍 How it Works (The Nuance)
One confusing detail is that /dev/tcp is not an actual file on your disk. If you try to run ls /dev/tcp, you will find nothing.
As stated in 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.
Bash uses these paths as internal triggers to perform a DNS lookup and a connect(2) system call. The exec 3 > command then assigns this socket to file descriptor 3, allowing you to interact with it like a standard file.
The Request Logic
Mathematically, the HTTP request structure can be viewed as:
Data Flow Diagram
⚠️ Limitations and Compatibility
While powerful, this method is a "raw" approach. It lacks the sophisticated features of a dedicated client.
| Feature | curl | Bash /dev/tcp |
|---|---|---|
| TLS/SSL (HTTPS) | ✅ | ❌ |
| Redirect Handling | ✅ | ❌ |
| Chunked Encoding | ✅ | ❌ |
| Compression (gzip) | ✅ | ❌ |
| Automatic Retries | ✅ | ❌ |
Critical Caveats:
- Connection Hanging: By default, HTTP/1.1 keeps connections open. If you omit
Connection: close, thecat 3command will hang indefinitely waiting for more data. To mitigate this, you can wrap the command in a timeout:timeout 6 bash -c '...'. - Plaintext Only: This only works for
http://. Forhttps://, you would needopenssl s_client, at which point you might as well installcurl. - Shell Support: This is a Bash-specific feature. It will not work in
zshordash(the default/bin/shon Debian).
📋 Pre-flight Checklist
Before relying on this in a script, verify the following:
- Is the shell actually
bash? - Was bash compiled with the
--enable-net-redirectionsflag? - Is the target hostname resolvable via DNS?
- Is the connection plaintext (non-TLS)?
Final Thought: While most modern builds enable this feature, some minimal Debian distributions have shipped with it disabled. However, in a locked-down container where you cannot install new packages, this is an invaluable trick for quick connectivity checks.