A minimal HTTP server for Unix/Linux systems using fork-based concurrency. Simple, lightweight, and easy to understand.
This server is designed for development and learning purposes only. It should NOT be used in production environments without thorough security review and hardening. While recent updates have addressed many security issues, it is still a minimal implementation without comprehensive protection.
- Operating System: POSIX-compliant Unix/Linux (macOS, Linux, BSD)
- Compiler: GCC or Clang with C99 support
- Build Tool: GNU Make
- Dependencies: Standard C library, POSIX headers
- Simple routing with
GET(),POST(), andHEAD()macros - Static file serving from
./publicdirectory - Directory listing - nginx-like automatic index with file sizes and dates
- Auto index.html - Automatic detection in subdirectories
- Request header parsing and access
- POST payload handling
- HEAD method support for checking resource existence
- Fork-based concurrency (up to 1000 concurrent connections)
- Path traversal protection
- Graceful shutdown with signal handling
✅ Path traversal attack prevention
✅ Buffer overflow protection
✅ Format string vulnerability fixes
✅ NULL pointer dereference checks
✅ Input validation for HTTP requests
make./server # Default port 8000
./server 3000 # Custom portOpen in your browser:
- http://localhost:8000/ - Index page or default greeting
- http://localhost:8000/test - View system info & request headers
- http://localhost:8000/[any-file] - Serve static files from
./public
Press Ctrl+C for graceful shutdown.
#include "httpd.h"
int main() {
serve_forever("8000");
return 0;
}void route() {
ROUTE_START()
GET("/hello") {
HTTP_200;
printf("Hello, World!\n");
}
GET("/test") {
HTTP_200;
// Display system information
printf("Server Uptime: ...\n");
printf("OS: %s\n", ...);
// Full implementation in main.c
}
POST("/data") {
HTTP_201;
printf("Received %d bytes\n", payload_size);
// Access POST data via: payload, payload_size
}
HEAD("/file.pdf") {
// Check if resource exists without sending body
if (file_exists("public/file.pdf")) {
HTTP_200;
// Headers sent, no body (HEAD behavior)
} else {
HTTP_404;
}
}
GET(uri) {
// Catch-all route for static files
serve_static_file(uri);
}
ROUTE_END()
}// Get specific header
char *user_agent = request_header("User-Agent");
// Get all headers
header_t *headers = request_headers();
while (headers->name) {
printf("%s: %s\n", headers->name, headers->value);
headers++;
}
// Access request components
printf("Method: %s\n", method); // "GET" or "POST"
printf("URI: %s\n", uri); // "/path/to/resource"
printf("Query: %s\n", qs); // "key=value&foo=bar"
printf("Protocol: %s\n", prot); // "HTTP/1.1"pico/
├── main.c # Entry point and example routes
├── httpd.c # HTTP server implementation
├── httpd.h # API header and macros
├── Makefile # Build configuration
├── README.md # Documentation
└── public/ # Static files directory
├── index.html
├── 404.html
└── ...
- Port: Pass as command-line argument (default: 8000)
- Public Directory:
./public(defined inmain.c) - Max Connections: 1000 (defined in
httpd.c) - Buffer Size: 65535 bytes (defined in
httpd.c)
- Fork-based concurrency: Creates a new process for each connection, which is inefficient for high-traffic scenarios
- No HTTPS support: Plain HTTP only
- No HTTP/2 or HTTP/3: HTTP/1.1 only
- Limited HTTP compliance: Minimal implementation, not fully HTTP/1.1 compliant
- No authentication: All endpoints are publicly accessible
- No compression: No gzip or other compression support
- File size limits: Large file uploads may hit buffer limits
Use Siege for load testing:
siege -i -f urls.txt
siege -c 100 -r 10 http://localhost:8000/Use fprintf(stderr, "message") for logging. All logs are sent to stderr.
fprintf(stderr, "Processing request: %s %s\n", method, uri);See main.c for a complete example. Key steps:
- Include
httpd.h - Implement
route()function with your endpoints - Call
serve_forever(port)inmain() - Compile with:
gcc -o myserver main.c httpd.c
void serve_forever(const char *port)- Start server on specified port
HTTP_200- Send 200 OK responseHTTP_201- Send 201 Created responseHTTP_404- Send 404 Not Found responseHTTP_500- Send 500 Internal Server Error response
ROUTE_START()- Begin route definitionGET(path)- Define GET routePOST(path)- Define POST routeHEAD(path)- Define HEAD route (returns headers only, no body)ROUTE_END()- End route definition
char *request_header(const char *name)- Get specific header valueheader_t *request_headers(void)- Get all headers array
char *method- HTTP method ("GET", "POST")char *uri- Request URIchar *qs- Query stringchar *prot- HTTP protocol versionchar *payload- POST request bodyint payload_size- POST body size in bytes
See CONTRIBUTING.md for guidelines.
Report security issues via the repository issues page. See SECURITY.md for details.
MIT License - see LICENSE file for details.
Reworked and refactored from https://gist.github.com/laobubu/d6d0e9beb934b60b2e552c2d03e1409e.
Based on http://blog.abhijeetr.com/2010/04/very-simple-http-server-writen-in-c.html.