← Back to news

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

mareksuppa.com|326 points|160 comments|by mrshu|Jun 16, 2026

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 curl and wget are both missing, and there are no other utilities available to open a network socket.

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.

Network Connectivity Concept


🛠️ 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, service represents 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: Request=Request Line+(Headers)+Empty Line+Body\text{Request} = \text{Request Line} + \sum(\text{Headers}) + \text{Empty Line} + \text{Body}

Data Flow Diagram


⚠️ Limitations and Compatibility

While powerful, this method is a "raw" approach. It lacks the sophisticated features of a dedicated client.

FeaturecurlBash /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, the cat 3 command 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://. For https://, you would need openssl s_client, at which point you might as well install curl.
  • Shell Support: This is a Bash-specific feature. It will not work in zsh or dash (the default /bin/sh on 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-redirections flag?
  • 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.