I Built a Small HTTP Server in C to Understand How the Web Actually Works

Published: (January 10, 2026 at 03:27 PM EST)
3 min read
Source: Dev.to

Source: Dev.to

What I Wanted (and What I Didn’t)

This project was never about building a production server.
I wasn’t trying to compete with Nginx. I only wanted answers to very basic questions:

  • What does an HTTP request really look like?
  • How does a server decide what file to return?
  • What happens between accept() and a browser rendering a page?

That’s it.

What This Server Actually Does

Here’s the exact scope — nothing more, nothing less:

  • Listens on port 8080
  • Handles GET requests only
  • Serves static files (HTML, text, images)
  • Uses POSIX threads (one client = one thread)
  • Sends proper Content-Type headers
  • Returns a plain 404 when a file doesn’t exist

It’s small enough to understand in one sitting.

Code Structure (Because One File Is a Nightmare)

I didn’t want everything dumped into main.c, so I split things up:

  • main.c → socket setup, bind, listen, accept loop
  • request.c → reads the request and handles the client
  • http.c → builds HTTP responses
  • utils.c → MIME types and URL decoding

Nothing fancy. Just separation of responsibility. This alone made debugging way less painful.

The First “Oh Damn” Moment

When I printed the raw HTTP request to the terminal for the first time, it finally clicked:

GET /index.html HTTP/1.1
Host: localhost:8080

That’s it. No objects. At that point, HTTP stopped feeling mysterious.

Parsing Without Overthinking

I didn’t write a full HTTP parser. I used a small regex to extract the file path after GET /, decoded it, and moved on.

Is it fragile? Yes. Over‑engineering would’ve killed the whole point of this project.

Building the Response Manually

Another underrated realization: a valid response is literally:

status line
headers
(empty line)
body

Once I hard‑coded that flow, everything made sense. When a file wasn’t found, I sent back a simple 404.

MIME Types: Small Detail, Big Lesson

At one point, HTML worked… but images didn’t. Turns out browsers really care about Content-Type. So I added a small extension → MIME mapping:

  • .htmltext/html
  • .jpgimage/jpeg
  • .pngimage/png

Suddenly, everything rendered correctly. That tiny bug taught me more than a dozen blog posts ever did.

Why C?

Because C doesn’t hide anything. If something works, it’s because you made it work. Writing this in C forced me to:

  • understand sockets properly
  • manage memory carefully
  • read actual system calls instead of abstractions

And honestly, it made me appreciate modern frameworks way more.

What This Server Is Bad At (On Purpose)

Let’s be clear — this server does not handle:

  • POST requests
  • HTTPS
  • Keep‑alive connections
  • Security
  • Real‑world traffic

And that’s fine. This was a learning project, not a startup.

Final Thoughts

If you’ve never built a server without a framework, I think you should try it once. Not to replace your tools, and you don’t need to finish it. For me, this project removed a lot of mental fog around HTTP. And that alone made it worth it.

Thanks for reading 🙌

Back to Blog

Related posts

Read more »

TCP Doesn’t Know What a Message Is

When I was working with HTTP, I carried a quiet assumption in the back of my mind: > if I send one thing, the other side receives one thing. It felt obvious. Al...

Todo App

Introduction After completing my first logic‑focused project Counters, I wanted to take the next natural step in complexity — not by improving the UI, but by c...