HN Debrief

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

  • Programming
  • Infrastructure
  • Security
  • Developer Tools
  • Open Source

The post is a short demo of using Bash’s built-in `/dev/tcp/host/port` redirection to open a TCP connection and manually send an HTTP request. The setup was practical, not theoretical: a minimal Docker image on an internal network had neither `curl` nor `wget`, and the author needed a fast way to check whether one container could reach another. That framing held up. People mostly treated this as a good emergency trick for health checks, localhost probes, captive environments, penetration testing, and tiny systems where adding packages is awkward or impossible.

Keep this in your toolbox for emergency connectivity checks, health probes, and low-privilege shells. For anything unattended or Internet-facing, install or call a real client because raw Bash socket tricks skip TLS, HTTP parsing, redirects, and all the ugly edge cases `curl` already solved.

Discussion mood

Positive about the trick as a neat and genuinely useful fallback, but sharply skeptical of using it beyond quick debugging. The biggest reasons were protocol complexity, missing TLS and modern HTTP support, and the reality that most production problems are better solved with `curl`, `nc`, Python, or a slightly less austere container image.

Key insights

  1. 01

    Raw TCP is not an HTTP client

    What Bash gives you here is socket redirection, not an HTTP stack. That changes the whole risk profile. A hand-rolled request is fine for poking a health endpoint, but the moment you need redirects, chunked bodies, proxy behavior, or even correct parsing, you are rebuilding a broken subset of `curl` by accident. One comment even corrected the related claim that you can do a full web server in pure Bash, noting that the cited project uses a C loadable module to listen for incoming connections.

    Use this as a byte-level debugging primitive, not as a reusable transport layer. If a script may survive past a one-off incident, switch it to a maintained client before it becomes hidden infrastructure.

      Attribution:
    • basilikum #1
    • mrshu #1
    • tombert #1
    • iam-TJ #1
  2. 02

    Distroless images trade CVE noise for operability pain

    The strongest container discussion was not about Bash at all. It was about why this trick keeps resurfacing. Distroless and `FROM scratch` images cut unused binaries and reduce scanner findings, but they also remove the basic tools people reach for during outages. The practical view was that this does reduce compliance churn, yet it mostly pushes debugging cost onto operators, who then need sidecars, ephemeral debug containers, or hacks like `/dev/tcp` to answer simple questions.

    If you run very slim images, decide in advance how operators will inspect networking and process state under pressure. A debug-container workflow is fine, but it needs to be standard and fast or people will keep reaching for shell hacks.

      Attribution:
    • OptionOfT #1
    • xmodem #1 #2 #3
    • regularfry #1
    • NewJazz #1
  3. 03

    Locked-down shells keep this technique relevant

    Several people knew `/dev/tcp` from security work, not Docker. In penetration tests, CTFs, and post-compromise shells, it is useful precisely because `curl`, `wget`, `nc`, and sometimes Python are missing or forbidden. Others used it to bypass overzealous corporate restrictions or to talk to local daemons through `/dev/udp` without shipping extra control utilities. That is the concrete reason this ancient feature still matters.

    Assume removing common binaries does not remove networking capability if Bash is still present. For defenders, hardening policies should model shell built-ins and interpreters, not just package allowlists.

      Attribution:
    • washbasin #1
    • Sohcahtoa82 #1
    • pickle-wizard #1
    • m3047 #1
    • nesarkvechnep #1
  4. 04

    Tiny systems and local health checks are the sweet spot

    The best real-world use cases were boring in a good way. One comment described squeezing heartbeat logic into routers with about 4 MB of flash where `libcurl` was too heavy. Others used the trick in container `HEALTHCHECK`s, especially when a service listened only on localhost and the image lacked normal HTTP tools. That pattern fits the feature well because the request is simple, the network path is controlled, and failure modes are easy to reason about.

    If you keep this in production at all, confine it to narrow probes inside your own boundary. Use it for a fixed endpoint on a trusted local network, not for general outbound HTTP.

      Attribution:
    • saidinesh5 #1
    • monkpit #1
    • figmert #1
    • nedt #1
    • geoctl #1
  5. 05

    Manual protocol work still teaches the right abstraction

    The nostalgic comments were not just reminiscing. They made a sharper point about learning. Typing SMTP, POP3, or HTTP by hand gives you the “nothing here is magic” moment that turns protocols into understandable text and state machines. One commenter noted that this gets harder with TLS, HTTP/2, and binary protocols, but the educational value remains because the core lesson is that systems are built from inspectable layers, not black boxes.

    Have junior engineers handcraft at least one request over a raw socket. It is still one of the fastest ways to build intuition for headers, framing, and where client libraries stop and the protocol begins.

      Attribution:
    • xenadu02 #1
    • vbezhenar #1
    • eqmvii #1
  6. 06

    The Python image made Bash the second-best hack

    One subtle but useful correction was that the specific container already had Python installed, because it was a `python:3.12-slim` image. A one-liner with `socket` or `urllib` would have been simpler and less error-prone than hand-formatting HTTP in Bash. The author agreed immediately. That makes the story less about a superior trick and more about what people forget is already in the box when debugging under time pressure.

    Before adding packages or reaching for obscure shell features, inventory the runtime you already have. In many slim images, the language runtime itself is your best emergency client.

      Attribution:
    • mrshu #1 #2
    • KomoD #1

Against the grain

  1. 01

    Bash networking is a bad interface

    This view rejected the feature on design grounds, not just safety grounds. The complaint was that overloading fake device paths into a shell is an ugly abstraction that mostly helps people tunnel around restrictions or exfiltrate data from poorly locked-down systems. That framing is harsher than the prevailing “nice trick, wrong tool” response, but it usefully questions whether convenience features in general-purpose shells quietly expand attack surface.

    If you care about shell minimization, audit built-ins as well as binaries. The dangerous capability may be in the interpreter, not in the package list.

      Attribution:
    • Retr0id #1
  2. 02

    HTTP/1.0 is simpler here

    One commenter argued the example should use HTTP/1.0 and skip `Connection: close` entirely. For this narrow use case, that is a cleaner fit because you want the server to close the socket and end the response without relying on keep-alive semantics. It is a small point, but it makes the copy-paste probe less fragile.

    For manual one-shot probes, prefer the simplest protocol version the server will accept. Fewer headers usually means fewer surprises when you are debugging under pressure.

      Attribution:
    • tzot #1

In plain english

/dev/tcp
A Bash feature that treats a path like `/dev/tcp/host/port` as a request to open a TCP network connection instead of a real file.
container
A lightweight isolated runtime environment that packages an application and some or all of its dependencies.
distroless
A style of container image that includes only the application and minimal runtime components, with few or no standard operating system tools.
Docker
A platform for packaging and running software in isolated containers.
FROM scratch
A Docker image built from an empty base with no operating system files included by default.
HTTP
Hypertext Transfer Protocol, the standard application protocol used by web browsers and web services.
HTTP/1.0
An older version of HTTP that typically closes the connection after each response unless told otherwise.
HTTP/2
A newer version of HTTP that uses a binary framing format and multiplexes multiple requests over one connection.
nc
Netcat, a command-line utility for opening and inspecting network connections.
POP3
Post Office Protocol version 3, an older protocol for retrieving email from a server.
SMTP
Simple Mail Transfer Protocol, the protocol used to send email between clients and mail servers.
TCP
Transmission Control Protocol, the basic Internet protocol that provides reliable ordered byte streams between two endpoints.
TLS
Transport Layer Security, the encryption layer used to secure HTTPS and many other network protocols.

Reference links

Shell and Bash networking references

Container debugging and operations

HTTP and proxy debugging tools

Captive portal and plain HTTP references