Minimal Static File HTTP Server
Build a tiny web server written entirely in Bash, which serves the files in a folder to a web browser. It listens for connections with socat (or netcat), reads the raw HTTP request text line by line, finds the file that was asked for, and writes back a correct HTTP response with headers and the file content. It returns 404 for missing files and blocks attempts to escape the folder with ../. It's not meant for real use, but it shows exactly what happens under the hood of every website. Run it only on your own machine. You'll learn the HTTP protocol, how sockets connect programs, handling input safely in shell scripts, MIME types, and logging.
- Estimate
- ~9.5h
- Steps
- 6
- Completed by
- 0
- Proposed by
- codeseed.app
netcat · socat · mimetype detection
Project roadmap
- 01
Read a raw HTTP request
~1.5hWrite handler.sh, which reads the request from standard input: the request line (method, path, version), then header lines until an empty line. Remove the carriage returns at the end of each line (HTTP lines end with \r\n). Start it for each connection with socat TCP-LISTEN:8080,reuseaddr,fork EXEC:./handler.sh, listening on 127.0.0.1 only. Log the request line to stderr. Done when: opening http://localhost:8080/ in a browser logs the request line.
- 02
Find the file and its type
~1.5hTake the path, decode %20 and other percent-encoded characters, and remove the query string. Map / to index.html. Build the full path under the document root, and choose the Content-Type from the extension with a case statement (html, css, js, png, jpg, svg and txt, with application/octet-stream as the default), or with file --mime-type. Done when: the handler logs the right file and type for several requests.
- 03
Send a valid response
~1.5hWrite the status line HTTP/1.1 200 OK, then the Content-Type, Content-Length (from stat or wc -c), Connection: close and an empty line, all with \r\n line endings, then send the file with cat so binary files like images arrive intact. Support HEAD requests (headers only). Done when: a browser shows an HTML page with its CSS and images.
- 04
Handle errors safely
~2hReturn 404 with a small HTML page for missing files, 405 for methods other than GET and HEAD, and 400 for malformed request lines. Resolve the real path with realpath and return 403 when it's outside the document root, which blocks /../../etc/passwd, including encoded forms such as %2e%2e. Never pass request text to eval or unquoted commands. Done when: curl --path-as-is http://localhost:8080/../../etc/passwd gets 403.
- 05
Add options and logging
~1.5hWrite server.sh, which takes the port and document root as options, checks that the folder exists, and starts socat. Log each request in a common log format (time, client address from the SOCAT_PEERADDR variable, request line, status and size) to a file. Stop cleanly on Ctrl+C. Done when: every request appears in the log with the right status.
- 06
Test it
~1.5hWrite tests with bats-core that start the server on a free port with a test folder and use curl to check: correct content and headers for HTML and an image, 404, 403 for traversal attempts, 405, HEAD requests and URL-encoded names. Run shellcheck on the scripts. Done when: the tests pass, and shellcheck reports no warnings.
Resources
Ready to build this?
Get a GitHub repo and start building. Your AI reviewer checks each step as you go.
Tech stack