Skip to content
Dev Tools Article

HTTP Requests Without Curl: The Bash /dev/tcp Trick

Learn how to fire raw HTTP requests using Bash's built-in TCP pseudo-device in stripped-down container environments.

Lenn Voss
Lenn Voss
Cloud & Infrastructure Writer · Jun 16, 2026 · 4 min read

You have likely been there: you are exec'd into a highly optimized, stripped-down Docker container to debug a networking issue. You need to verify if your container can reach a neighboring service over an internal network. You type curl http://service:8642/health only to get a cold, hard bash: curl: command not found. You try wget. Nothing. You are trapped in a minimal environment with no standard network utilities.

But if you have GNU Bash, you actually have a raw TCP client built right into your shell. Using the built-in /dev/tcp pseudo-device, you can speak HTTP directly to a socket without installing a single package.

The Magic of the Pseudo-Device

Before you run ls /dev/tcp to see what is there, save yourself the keystrokes. It does not exist on disk. If you try to list it or read it directly from another shell, you will just get an error.

As the Bash manual explains, /dev/tcp/host/port is a pseudo-device. It is an internal redirection handled entirely by Bash itself. The creators of Bash chose this naming convention because no standard Unix system has a real /dev/tcp or /dev/udp directory hierarchy, meaning there is zero risk of colliding with actual system files.

When you reference /dev/tcp/host/port, Bash intercepts the path, performs the DNS lookup, and executes a standard connect(2) system call under the hood. It is a clever way to expose low-level networking directly to shell scripts.

Crafting a Raw HTTP Request

To use this pseudo-device, you need to open a file descriptor, write your HTTP request, and read the response. Here is how you can perform a quick health check against an internal service:

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

Let's break down exactly what is happening here:

  1. exec 3<>/dev/tcp/service/8642: This opens file descriptor 3 for both reading and writing (<>) and points it to the TCP socket connected to service on port 8642.
  2. printf ... >&3: We write a raw HTTP/1.1 GET request directly into file descriptor 3. Notice the explicit use of \r\n (carriage return and line feed) to conform to the HTTP specification.
  3. cat <&3: We read the response back from file descriptor 3 and print it to standard output. This will output everything: the HTTP status line, headers, the blank line, and the response body.

If you need to pass authentication headers, such as a Bearer token, you can simply inject them before the final blank line:

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

The Crucial Gotchas

While this trick is incredibly handy, it is a raw socket connection, not a full-featured HTTP client. There are several critical gotchas you must keep in mind:

  • The Connection: close Header is Mandatory: In HTTP/1.1, connections are kept alive by default. If you omit Connection: close, the server will keep the socket open after sending the response. As a result, cat <&3 will hang indefinitely, waiting for an EOF that never arrives. Specifying Connection: close forces the server to close the connection, allowing cat to finish. To safeguard against hangs, you can wrap your command in a timeout: timeout 6 bash -c '...'.
  • No TLS Support: Because /dev/tcp opens a raw, unencrypted TCP socket, this trick only works for plaintext HTTP. If you need HTTPS, you are out of luck unless you have openssl s_client available—and if you have that, you might as well use proper tools.
  • It is Bash-Only: This is a proprietary Bash feature, not a POSIX standard. Alternative shells like dash (the default /bin/sh on Debian) and zsh do not support /dev/tcp redirections. You must run your commands explicitly inside a Bash shell.
  • Compile-Time Dependencies: The feature is only available if Bash was compiled with the --enable-net-redirections flag. While almost all modern, mainstream distributions enable this by default, Debian famously shipped Bash with this feature disabled for years. If you are working in an older or highly customized minimal environment, you may want to verify support first.

When to Use It

For day-to-day development and scripting, curl remains the gold standard. It handles redirects, chunked encoding, compression, retries, and SSL/TLS negotiation without you needing to think about it.

But when you are locked inside a production container with zero package managers and a broken service, knowing how to manually wire up a file descriptor to a TCP socket is a lifesaver. It is a lightweight, dependency-free debugging tool hidden right inside your shell.

Sources & further reading

  1. TIL: You can make HTTP requests without curl using Bash /dev/TCP — mareksuppa.com
Lenn Voss
Written by
Lenn Voss · Cloud & Infrastructure Writer

Lenn writes about cloud platforms, Kubernetes internals, and the infrastructure decisions that quietly make or break engineering organizations. Based in Berlin's vibrant tech scene, they have a talent for turning dense platform-engineering topics into prose that people actually finish reading.

Discussion 0

Join the discussion

Sign in or create an account to comment and vote.

No comments yet

Be the first to weigh in.

Related Reading