How I Built a Web Server in Assembly (From Scratch!)

Ashbal FatimaAshbal Fatima
5 min read

Have you ever wondered what happens under the hood when you type a URL and hit enter? What if you could write a web server from scratch β€” not in C or Python, but in pure x86_64 Assembly, using only Linux system calls?

That’s exactly what I did.

This blog walks you through how I built a minimal HTTP server that can handle GET and POST requests β€” without any standard library, high-level abstractions, or frameworks. Just syscalls, raw sockets, forking, and manual parsing β€” all in under 500 lines of Assembly.

🧠 Why Assembly?

The assembly gives you ultimate control over what the CPU does. I wanted to understand how:

  • sockets are created and accepted,

  • HTTP requests are parsed,

  • file I/O is handled,

  • processes fork and manage connections.

And there’s no better way than writing everything manually β€” byte by byte.

βš™ Features of My Web Server

  • βœ… Serves files via GET

  • βœ… Saves data via POST

  • βœ… Handles concurrent clients with fork

  • βœ… No dependencies β€” not even libc

  • βœ… Built using Linux syscalls only

  • βœ… Manual parsing of HTTP/1.0 headers

⚠ Limitations β€” On Purpose!

Like any handcrafted project built for learning, my assembly-based web server comes with a few intentional limitations. These aren’t flaws β€” they’re features that helped me focus on core concepts without overengineering. Here's what you won't find in my server (and why):

  • No Persistent Connections (Keep-Alive):
    Each HTTP request is handled independently. Once the response is sent, the connection closes. Simpler to manage β€” especially in assembly.

  • Only Supports HTTP/1.0:
    I kept it old-school. HTTP/1.0 is easier to implement and perfect for understanding the basics of request-response flow.

  • No MIME Type Detection:
    Everything served is treated as plain text, whether it's .html, .css, or even .png. This allowed me to sidestep content negotiation logic.

  • No Request Size Limits or Timeout Handling:
    There’s no safety net for overly large requests or hanging connections. This tradeoff gave me cleaner code and clearer performance profiling.

  • Not Production-Ready (And That’s Okay!):
    This server isn’t designed for hosting real-world apps. It’s built purely for learning how low-level networking, syscalls, and performance tracing really work.


🧾 Overview of Server Workflow

_start
β”œβ”€β”€ socket β†’ bind β†’ listen
└── Loop:
    β”œβ”€β”€ accept
    β”œβ”€β”€ fork
    β”œβ”€β”€ In child:
    β”‚   β”œβ”€β”€ read request
    β”‚   β”œβ”€β”€ parse GET/POST
    β”‚   β”œβ”€β”€ handle file I/O
    β”‚   └── send HTTP response
    └── In parent:
        └── close client socket

🧡 Key Snippets (Explained Simply)

1. Accept + Fork for Concurrent Clients

mov rax, 43        ; syscall number for accept
syscall
mov r12, rax       ; save new socket FD

mov rax, 57        ; syscall number for fork
syscall

2. Parse HTTP Method and Path

lodsb              ; load byte from request
cmp al, ' '        ; stop at space
je done_copying

3. Write HTTP Response

mov rdi, 4         ; client socket FD
lea rsi, [msg]     ; HTTP/1.0 200 OK header
mov rdx, 19        ; length of message
mov rax, 1         ; syscall: write
syscall

πŸ“₯ POST Request Handling (Explained Simply)

  1. The server reads the POST /file.txt HTTP/1.0 line.

  2. It extracts the file path (file.txt).

  3. It reads until the HTTP headers end (\r\n\r\n).

  4. The remaining body is saved to file.txt.

  5. Returns a simple 200 OK.


πŸ“€ GET Request Handling

  1. Extracts the path from GET /file.txt HTTP/1.0.

  2. Opens the file and reads its contents.

  3. Sends back the data with a 200 OK header.


πŸ“‚ Memory Buffers Used

BufferPurpose
bufferStores incoming request
open_pathStores the path from the URL
read_fileStores file contents or the POST body
bytes_readTracks how many bytes were read
socket_addrStruct for bind and accept

πŸ” Key Syscalls Used

  • socket(): Create the server socket.

  • bind(): Bind it to port 80.

  • listen(): Start listening.

  • accept(): Accept a new client connection.

  • fork(): Spawn a new child to handle it.

  • read(): Read HTTP request.

  • open(), read() (again): Load file (GET) or parse body (POST).

  • write(): Send HTTP response.

  • close(): Close descriptors.

Each of these had to be done manually, including setting up syscall numbers, managing memory and buffers, and handling string comparisons in assembly.


πŸš€ How to Compile and Run

nasm -f elf64 server.asm -o server.o
ld server.o -o server

Step 2: Run with Root Privileges (port 80)

sudo ./server

Now try:

curl http://localhost/file.txt          # GET
curl -X POST -d "Hello" /file.txt      # POST

🀯 The Pain Points

  • Parsing HTTP in assembly? Not fun. I had to scan for newline characters, spaces, and manually extract the path.

  • Memory management: There’s no malloc here β€” you predefine buffers or use syscalls like mmap.

  • Concurrency: Forking worked, but it took some tweaking to make sure sockets were closed properly.

  • Debugging: GDB was my best friend. I stared at syscall return codes like my life depended on it.


πŸ’‘ What I Learned

This wasn’t just a coding challenge. It was a deep dive into:

  • How Linux syscalls really work

  • File descriptors and sockets at a low level

  • Parsing raw text (like HTTP) in memory

  • The elegance (and brutality) of assembly

It gave me a new appreciation for the layers of abstraction modern languages offer β€” and what lies underneath.


πŸ”š Final Thoughts

Building a web server in assembly might sound unhinged β€” and honestly, it kind of is. But that’s exactly what makes it powerful. It strips away abstractions and forces you to face how the web and operating systems actually work at the lowest level.

If you're diving into cybersecurity β€” especially exploit development or red teaming β€” this kind of project is gold. It teaches you to see what most people miss: what really happens under the hood when packets move, syscalls fire, and memory is allocated.

This isn’t just about writing a web server. It’s about thinking like a hacker.

The full technical breakdown is up on my GitHub writeup. Would love to hear your thoughts or improvements β€” let’s geek out!

0
Subscribe to my newsletter

Read articles from Ashbal Fatima directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Ashbal Fatima
Ashbal Fatima

Penetration Testing enthusiast with hands-on experience in offensive security and red teaming. Skilled in web, network, and Linux security using Hack The Box, pwn.college, and PortSwigger. Focused on practical exploitation, vulnerability analysis, and building a strong offensive toolkit. Currently expanding into cloud, AWS, and WiFi pentesting.