惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Martin Fowler
Martin Fowler
Blog — PlanetScale
Blog — PlanetScale
Vercel News
Vercel News
L
LangChain Blog
Google DeepMind News
Google DeepMind News
H
Hackread – Cybersecurity News, Data Breaches, AI and More
F
Fortinet All Blogs
The GitHub Blog
The GitHub Blog
Recent Announcements
Recent Announcements
D
DataBreaches.Net
云风的 BLOG
云风的 BLOG
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
A
About on SuperTechFans
博客园_首页
N
Netflix TechBlog - Medium
Y
Y Combinator Blog
Hugging Face - Blog
Hugging Face - Blog
Last Week in AI
Last Week in AI
酷 壳 – CoolShell
酷 壳 – CoolShell
B
Blog
Apple Machine Learning Research
Apple Machine Learning Research
罗磊的独立博客
美团技术团队
V
V2EX

Lobsters

CIFSwitch: a non-universal Linux local root vulnerability RIPE NCC session fixation: poaching logins with an Atlas probe GNOME 2.20 but its Web Components Agentic Search for Context Engineering – Leonie Monigatti Garnix is shutting down [not OC] akashina.tngl.sh/jjc Concerning Emacs (and Jazz) Nitpicking the shell history scene in ‘Tron: Legacy’ What's cooking on SourceHut? Q2 2026 The tenth OpenPGP email summit Package managers that package package managers Clojure on Fennel part three: parsing WordPress at 23 Finding Miscompiles for Fun, Not Profit GitHub - creusot-rs/creusot: Creusot helps you prove your Rust code is correct. Announcing Rust 1.96.0 | Rust Blog A Love Letter to Neovim sqlite AGENTS.md Am I a Bad Friend? CSS vs. JavaScript • Josh W. Comeau Erlang Ecosystem Foundation - Supporting the BEAM community A brief note about slot access cost in Common Lisp Keyboard latency probe Rethinking the GNOME clipboard issues Back to the Building Blocks’ Building Blocks Tech Notes: Theseus: translating win32 to wasm Fast is better than slow Content-addressed Rust builds (or, what kache actually caches) Intent to Prototype: Embedding API Canada’s Bill C-22 and the security cost of collecting more data
Making HTTP requests from a container that has no curl, u...
Marek Šuppa · 2026-06-17 · via Lobsters

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:

bash

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:

bash

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 curl quietly does for you. It’s a quick connectivity and debugging trick.
  • The Connection: close header matters. Without it the server keeps the connection open after it responds, which is the HTTP/1.1 default, and cat <&3 then waits forever for bytes that never arrive. Asking the server to close means cat reaches EOF and returns. Wrapping the call in timeout 6 bash -c '...' covers you either way.
  • There’s no TLS. /dev/tcp opens a raw socket, so this only works for plaintext HTTP. For https you’d need openssl s_client, and by then you may as well have the proper tools.
  • This is a bash feature, not POSIX. dash (Debian’s /bin/sh) and zsh don’t have it, so a #!/bin/sh script can’t use it. Call bash directly.
  • It’s a compile-time option, switched on when bash is 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.