Create Basic Web Server in Python: A Step-by-Step Guide
Learn how to create a basic web server in Python using built-in modules. This practical guide covers HTTP server setup for local development.

How to Create a Basic Web Server in Python
Learning to create a basic web server in Python is a foundational skill for anyone delving into web development, testing local applications, or simply serving static files. Python, with its rich standard library, offers straightforward ways to set up a functional web server with minimal code. This guide will walk you through the process using Python's built-in modules, explaining the concepts along the way, and providing practical examples.
A web server is essentially a program that accepts requests from clients (like your web browser) and sends back responses, often in the form of web pages, images, or other files. While Python isn't typically used for high-traffic production web servers without frameworks like Django or Flask, its built-in server capabilities are incredibly useful for development, testing, and sharing files on a local network.
Understanding the Basics: HTTP and Web Servers
Before diving into code, let's briefly touch upon the underlying principles:
- HTTP (Hypertext Transfer Protocol): This is the communication protocol used for transmitting hypermedia documents, such as HTML. It defines how messages are formatted and transmitted, and what actions web servers and browsers should take in response to various commands.
- Client-Server Model: In web communication, your browser acts as the client, requesting resources, and the web server provides those resources.
- Ports: Web servers listen for requests on specific ports. The standard port for HTTP is 80, and for HTTPS (secure HTTP) it's 443. For local development, you'll often use higher-numbered ports (e.g., 8000, 8080) to avoid conflicts and permission issues.
Method 1: Using http.server for Static Files (Python 3)
The http.server module in Python 3 is the most common and easiest way to create a basic web server for serving static files. It's a direct replacement for the SimpleHTTPServer module found in Python 2.
Step-by-Step Implementation
Navigate to your desired directory: Open your terminal or command prompt and use the
cdcommand to navigate to the directory containing the files you want to serve. For example, if your files are in~/Documents/my_website:cd ~/Documents/my_websiteThe server will serve files relative to this directory, meaning if you request
http://localhost:8000/index.html, it will look forindex.htmlwithinmy_website.Start the server: Once in the correct directory, execute the following command:
python -m http.server 8000python -m: This tells Python to run a module as a script.http.server: This is the module containing the HTTP server functionality.8000: This specifies the port number the server will listen on. You can choose any available port, but 8000 is a common convention for local development.
You should see output similar to this:
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...Access your server: Open your web browser and navigate to
http://localhost:8000. You should see a directory listing of the current directory. If there's anindex.htmlorindex.htmfile in that directory, the server will automatically serve that file as the default page.
Customizing the Server with a Python Script
While the command-line approach is quick, you might want more control. You can achieve this by writing a small Python script.
Create a file named simple_server.py with the following content:
import http.server
import socketserver
PORT = 8000
Handler = http.server.SimpleHTTPRequestHandler
with socketserver.TCPServer(("", PORT), Handler) as httpd:
print(f"Serving at port {PORT}")
print(f"Access your server at http://localhost:{PORT}")
httpd.serve_forever()
Explanation:
import http.serverandimport socketserver: These lines import the necessary modules.http.serverprovides the HTTP server components, andsocketserveris a framework for network servers, whichhttp.serverbuilds upon.PORT = 8000: Defines the port number.Handler = http.server.SimpleHTTPRequestHandler: This class is responsible for handling requests. It serves files from the current directory and provides directory listings.with socketserver.TCPServer(("", PORT), Handler) as httpd:: This creates an instance of a TCP server.""(empty string) means the server will listen on all available network interfaces (e.g.,localhost, your local IP address).PORTis the port number.Handleris the class that will process incoming requests.
httpd.serve_forever(): This method starts the server and keeps it running indefinitely until you manually stop it (e.g., by pressingCtrl+Cin the terminal).
To run this script, navigate to the directory containing simple_server.py and execute:
python simple_server.py
Then, access http://localhost:8000 in your browser.
Key Features of http.server.SimpleHTTPRequestHandler:
- Serves static files: Automatically looks for files in the current directory and its subdirectories.
- Directory listing: If a URL corresponds to a directory and no
index.html(orindex.htm) is found, it will generate an HTML listing of the directory's contents. - Basic HTTP methods: Supports
GETrequests for retrieving files.
Method 2: Serving Custom Content with BaseHTTPRequestHandler
For more advanced use cases, where you want to handle specific URLs or generate dynamic content without a full-fledged framework, you can subclass http.server.BaseHTTPRequestHandler. This gives you fine-grained control over how requests are processed.
Example: A Basic REST-like API
Let's create a server that responds differently based on the URL path.
Create a file named custom_server.py:
import http.server
import socketserver
import json
import os # For getting current working directory
PORT = 8000
class CustomHandler(http.server.BaseHTTPRequestHandler):
def do_GET(self):
# Log the request path
print(f"Received GET request for: {self.path}")
if self.path == '/':
self.send_response(200)
self.send_header("Content-type", "text/html")
self.end_headers()
self.wfile.write(b"<h1>Welcome to my Custom Python Server!</h1>")
self.wfile.write(f"<p>Current directory: {os.getcwd()}</p>".encode('utf-8'))
elif self.path == '/api/data':
self.send_response(200)
self.send_header("Content-type", "application/json")
self.end_headers()
data = {"message": "Hello from the API!", "status": "success", "items": [1, 2, 3]}
self.wfile.write(json.dumps(data).encode('utf-8'))
elif self.path == '/hello':
self.send_response(200)
self.send_header("Content-type", "text/plain")
self.end_headers()
self.wfile.write(b"Hello there, visitor!")
else:
self.send_response(404)
self.send_header("Content-type", "text/html")
self.end_headers()
self.wfile.write(b"<h1>404 Not Found</h1>")
self.wfile.write(b"<p>The requested resource was not found.</p>")
def do_POST(self):
# Example for handling POST requests
print(f"Received POST request for: {self.path}")
if self.path == '/submit':
content_length = int(self.headers['Content-Length']) # Get the length of data
post_data = self.rfile.read(content_length) # Read the data
self.send_response(200)
self.send_header("Content-type", "text/html")
self.end_headers()
self.wfile.write(b"<h1>POST Request Received!</h1>")
self.wfile.write(f"<p>You sent: {post_data.decode('utf-8')}</p>".encode('utf-8'))
else:
self.send_response(404)
self.send_header("Content-type", "text/html")
self.end_headers()
self.wfile.write(b"<h1>404 Not Found for POST</h1>")
with socketserver.TCPServer(("", PORT), CustomHandler) as httpd:
print(f"Serving custom content on port {PORT}")
print(f"Access http://localhost:{PORT}")
print(f"Access http://localhost:{PORT}/api/data")
print(f"Access http://localhost:{PORT}/hello")
httpd.serve_forever()
Key methods and attributes of BaseHTTPRequestHandler:
self.path: A string containing the request path (e.g.,/,/api/data).self.headers: A dictionary-like object containing request headers.self.command: The HTTP method (e.g.,GET,POST).do_GET(),do_POST(),do_PUT(), etc.: You override these methods to handle specific HTTP request types.send_response(code): Sends the HTTP status code (e.g.,200for OK,404for Not Found).send_header(name, value): Sends an HTTP header. Common headers includeContent-type.end_headers(): Must be called after all headers are sent.wfile: A file-like object for writing the response body back to the client. Remember to send bytes (b"...")!rfile: A file-like object for reading the request body (useful forPOSTrequests).
To test this server:
- Save the code as
custom_server.py. - Run
python custom_server.pyin your terminal. - Open your browser and navigate to:
http://localhost:8000http://localhost:8000/api/datahttp://localhost:8000/hellohttp://localhost:8000/nonexistent(to see the 404)
- To test the POST request, you would typically use a tool like Postman,
curl, or a simple HTML form.- Example
curlcommand:curl -X POST -H "Content-Type: application/x-www-form-urlencoded" -d "name=Alice&age=30" http://localhost:8000/submit
- Example
This method gives you much greater control and flexibility to handle various types of requests and generate dynamic responses, making it a valuable tool for quick API prototyping or custom local servers.
Method 3: CGIHTTPServer for CGI Scripts (Python 2 & 3)
The CGIHTTPServer module (or http.server.CGIHTTPRequestHandler in Python 3) allows your web server to execute CGI (Common Gateway Interface) scripts. CGI scripts are executable programs that web servers can run to generate dynamic content. While largely superseded by modern web frameworks, understanding CGI can be useful for legacy systems or simple server-side scripting.
Using http.server.CGIHTTPRequestHandler (Python 3)
Prepare your CGI directory: Create a directory named
cgi-bin(orhtbin) inside the directory where you'll run your server. CGI scripts must typically reside in this special directory for the server to recognize them as executable.mkdir cgi-binCreate a simple CGI script: Inside
cgi-bin, create a file namedhello.pywith the following content. Crucially, make sure it's executable!#!/usr/bin/env python3 print("Content-type: text/html\n") print("<html>") print("<head><title>CGI Hello</title></head>") print("<body>") print("<h1>Hello from CGI!</h1>") print("<p>This content was generated dynamically by a Python CGI script.</p>") print("</body>") print("</html>")Make it executable (on Linux/macOS):
chmod +x cgi-bin/hello.pyRun the CGI server:
import http.server import socketserver PORT = 8000 Handler = http.server.CGIHTTPRequestHandler with socketserver.TCPServer(("", PORT), Handler) as httpd: print(f"Serving CGI content on port {PORT}") print(f"Access http://localhost:{PORT}/cgi-bin/hello.py") httpd.serve_forever()Access the CGI script: Open your browser and go to
http://localhost:8000/cgi-bin/hello.py. The server will executehello.pyand display its output.
Important Considerations for CGI:
- Shebang Line: The
#!/usr/bin/env python3line (shebang) tells the system which interpreter to use for the script. - Content-type Header: CGI scripts must print a
Content-typeheader followed by a blank line before any other output. This tells the browser how to interpret the response. - Executability: The CGI script file must have executable permissions.
- Security: CGI can be a security risk if not handled carefully, as it executes arbitrary scripts. Always ensure scripts are trusted and properly sanitized.
Choosing the Right Approach
| Feature | python -m http.server (or SimpleHTTPRequestHandler) |
BaseHTTPRequestHandler subclass |
CGIHTTPRequestHandler |
|---|---|---|---|
| Use Case | Serving static files, quick local testing | Custom routing, simple API, dynamic content | Executing server-side scripts |
| Ease of Use | Extremely easy | Moderate | Moderate, with setup |
| Dynamic Content | No (only serves existing files) | Yes, programmatic generation | Yes, script execution |
| HTTP Methods | GET only | Full control over GET, POST, PUT, etc. | GET/POST for script execution |
| Configuration | Command-line arguments | Python script customization | Directory structure, permissions |
| Common Application | Front-end development, image hosting, file sharing | Small-scale APIs, custom server logic | Legacy systems, specialized tasks |
Further Enhancements and Considerations
- Running in the background: For long-running servers, you might use tools like
nohuporscreen(on Linux/macOS) to keep the server running after you close the terminal. - Security: For anything beyond local development, these basic servers are not secure for production environments. They lack features like proper authentication, robust error handling, and protection against common web vulnerabilities.
- IPv6 and other interfaces: By default,
""binds to all available network interfaces (IPv4 and IPv6 if available). You can explicitly specify an IP address like"127.0.0.1"for IPv4 localhost only, or"0.0.0.0"for all IPv4 interfaces. - HTTP/HTTPS: These basic servers only support HTTP. Implementing HTTPS would require additional libraries (
ssl) and certificates, which is beyond the scope of a basic server setup. - Templating: For dynamic HTML generation, you'd typically integrate a templating engine like Jinja2 if not using a full framework.
- Concurrency: These basic servers are single-threaded and can only handle one request at a time. For concurrent requests, you'd need to explore
socketserver.ThreadingMixInorForkingMixIn, or more robust solutions likewsgirefcombined with a WSGI server (e.g., Gunicorn).
Conclusion
Python's built-in http.server module provides powerful and flexible ways to create a basic web server in Python for various local development needs. Whether you need to quickly serve static files, prototype a simple API, or even experiment with CGI, Python offers an accessible solution. While not suitable for production use, mastering these fundamental server concepts is an excellent stepping stone into more advanced web development with Python frameworks.
FAQ
Q1: What is the simplest way to start a web server in Python?
A1: The simplest way is to navigate to your desired directory in the terminal and run python -m http.server 8000. This will start a server serving files from the current directory on port 8000.
Q2: Is Python's built-in web server suitable for production environments?
A2: No, Python's built-in http.server module is designed for development, testing, and local file sharing. It is not optimized for performance, security, or robustness required for production web applications and should not be used in such scenarios.
Q3: How do I change the port number of the web server?
A3: When starting from the command line, you can specify the port number as an argument, e.g., python -m http.server 8080. In a Python script, you can simply change the PORT variable definition.
Q4: Can I serve a specific HTML file as the default page instead of a directory listing?
A4: Yes, if your directory contains a file named index.html (or index.htm), the http.server module will automatically serve that file as the default page when you access the server's root URL (e.g., http://localhost:8000/).
Q5: How can I handle dynamic requests with the basic Python server?
A5: For handling dynamic requests and custom routing, you should subclass http.server.BaseHTTPRequestHandler. This allows you to override methods like do_GET() and do_POST() to process different URL paths and generate responses programmatically.
Q6: What's the difference between SimpleHTTPRequestHandler and BaseHTTPRequestHandler?
A6: SimpleHTTPRequestHandler is a direct subclass of BaseHTTPRequestHandler that specifically implements functionality for serving files from the current directory and providing directory listings. BaseHTTPRequestHandler provides a more general framework, allowing you to implement your own logic for handling HTTP requests from scratch.