DEV Community

MuneebAhmedKhan-writing
MuneebAhmedKhan-writing

Posted on

Send an HTTP Request by Hand With Netcat

Type a raw HTTP request into a TCP socket, read back a real 200 OK, then watch one wrong character kill it. No browser, no library.

Prerequisites

  • A Linux terminal (any distro; a VM is fine)
  • bash or zsh
  • Internet access

Steps

1. Check for netcat and identify the variant.

which nc && nc -h 2>&1 | head -1
Enter fullscreen mode Exit fullscreen mode

Expect: a path such as /usr/bin/nc, then a line naming the build — OpenBSD netcat or Ncat 7.xx. Write down which one; steps 11 and 12 use a different flag on each. Anything else, such as a bare version like [v1.10-47], is netcat-traditional — use the flags exactly as written. If nothing prints, continue to step 2. Otherwise skip to step 3.

2. Install netcat.

sudo apt install netcat-openbsd     # Debian, Ubuntu
sudo dnf install nmap-ncat          # Fedora, RHEL
Enter fullscreen mode Exit fullscreen mode

On other distros, install your package manager's OpenBSD netcat package. Re-run step 1. Expect: a path and a variant name.

3. Confirm outbound port 80 works.

nc -vz example.com 80
Enter fullscreen mode Exit fullscreen mode

Expect: a success message naming the port. A timeout or a name-resolution error means your network blocks outbound port 80 or cannot resolve the host — nothing below will work until you move to a network that permits both. If -z is rejected, see Troubleshooting.

4. Store the request.

REQ='GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n'
Enter fullscreen mode Exit fullscreen mode

Use single quotes. They stop the shell from expanding anything inside the string, which matters once you add header values containing $ or a backtick.

5. Send the request.

printf '%b' "$REQ" | nc example.com 80 | tee response.txt
Enter fullscreen mode Exit fullscreen mode

%b makes printf interpret the \r\n escapes instead of printing them as text; tee writes to the file and the screen at once.

Expect: HTTP/1.1 200 OK, a block of headers, a blank line, then HTML. The body scrolls past — read it from the file in the steps below. Re-running overwrites the file.

6. Read the status line.

head -1 response.txt
Enter fullscreen mode Exit fullscreen mode

Expect: three parts in order — protocol version, status code, reason phrase.

7. Read the header block.

sed -n '1,/^\r$/p' response.txt
Enter fullscreen mode Exit fullscreen mode

Look for Content-Length or Transfer-Encoding, and for Allow. Any of them may be absent.

8. Open the whole response.

less response.txt
Enter fullscreen mode Exit fullscreen mode

Every header line ends in ^M, as do the chunk-size lines further down. That is the carriage return you sent, not corruption. Press q to exit.

9. Find the first line after the headers.

sed -n '/^\r$/{n;p;q;}' response.txt
Enter fullscreen mode Exit fullscreen mode

The {n;p;q;} advances one line past the blank line, prints it, and stops. Applies only if step 7 showed Transfer-Encoding: chunked. Expect: a short hex number such as 22f.

10. Read the end of the response.

tail -2 response.txt
Enter fullscreen mode Exit fullscreen mode

Applies only to a chunked response. Expect: a lone 0, then a line that looks blank. It isn't — it holds the carriage return from the terminating chunk's own CRLF, the same ^M you saw in step 8.

11. Break the terminator on purpose.

BAD='GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close/r/n/r/n'
printf '%b' "$BAD" | nc -w 5 example.com 80
Enter fullscreen mode Exit fullscreen mode

On nmap-ncat, use -i 5 instead of -w 5 — its -w times the connect only and will not close an idle socket.

Expect: nothing — no status line, no error, no body. The flag closes the socket after five idle seconds. Press Ctrl+C to exit early.

Silence is what you see inside that window. Drop the timeout and wait, and some servers eventually answer 408 Request Timeout — the same finding stated differently. The server never saw a complete request.

12. Repeat with more headers.

BAD='GET / HTTP/1.1\r\nHost: example.com\r\nUser-Agent: curl\r\nAccept: */*\r\nConnection: close/r/n/r/n'
printf '%b' "$BAD" | nc -w 5 example.com 80
Enter fullscreen mode Exit fullscreen mode

Same flag swap as step 11 on nmap-ncat. Expect: identical silence. Four headers behave exactly like two.

Verification check

printf '%b' "$REQ" | nc example.com 80 | head -1
Enter fullscreen mode Exit fullscreen mode

Exactly one line prints: HTTP/1.1 200 OK. Anything else means the request is malformed. If nothing prints, re-run step 3 — a success there means the port is open and the fault is in your request.

Troubleshooting

No output — the command sits, then returns to the prompt.
Copy the command rather than retyping it. The terminator needs backslashes: \r\n\r\n. printf gives forward slashes no special meaning, so /r/n/r/n lands inside the last header value as text and the terminator never gets sent.

Nothing happens and echo "$REQ" prints an empty line.
Re-run step 4. Shell variables do not survive a new terminal window or tab.

Step 11 or 12 hangs and never returns.
You are on nmap-ncat, where -w covers the connect only. Use -i 5. Ctrl+C to break out of the current run.

Your netcat rejects -z as an unknown option.
Not all builds include it. Use nc -vw 5 example.com 80 < /dev/null instead. -w is correct on every variant here, since this test only needs the connect to succeed or fail. The -v prints the result — without it you get silence whether the port is open or closed.

You expected 400 Bad Request and got silence.
Treat the hang as the normal symptom of a broken terminator, not a network fault. With no terminator, the server has nothing to reject — it is still listening.

Header values are not parsed as expected.
Put one space after every colon when you add your own headers. Host:example.com is widely tolerated but not guaranteed.

Closing

One wrong character — / for \ — is the difference between a page and total silence. HTTP is plain text over a socket, and it has no tolerance for text that is almost right.


Drafted and revised with Claude; every command tested by me.

Top comments (0)