Host Your Own AI Agent with OpenClaw - Free 1-Click Setup!

Python Web Server: Run It Locally, Then Host It in Production

A Python web server is software that listens for HTTP requests and returns web content — whether that’s a static file or a page generated on the fly. This guide covers the complete journey: spinning up a Python server on localhost for development and testing (Part 1), then deploying that same code to a VPS using Gunicorn, NGINX, and HTTPS so real users can reach it (Part 2).

What Is a Python Web Server?

A Python web server is any program that uses Python to receive HTTP requests and send back web responses. It can be Python’s built-in http.server module, a micro-framework like Flask, or a full-stack framework like Django — the common thread is Python handling the request–response cycle.

At its core, a Python web server is just a program (written in Python or running through it) that listens for HTTP requests and sends something back. Could be a static file, could be a page your framework builds on the spot.

Developers use them all the time. A Python web server works great for testing a web application before launch, building smaller sites, or serving up dynamic pages through frameworks like Flask or Django. Python scripts are quick to write and even quicker to tweak, so they slot right into a tight development cycle where you want to see changes now, not after some lengthy deployment process.

Web server comparison

But that simplicity cuts both ways. Here’s where a Python web server stacks up against Apache and NGINX, the two you’ll usually see in production:

FeaturePython Web ServerApacheNGINX
Ease of setupUsually just one command, barely any configTakes more work to set up properlyAbout the same effort as Apache, built for speed
FlexibilityGreat for dev work, testing, lightweight stuffSuper configurable for all kinds of production setupsHandles heavy traffic well, often used as a reverse proxy
PerformanceFine for development and smaller loadsBuilt to perform at scaleKnown for crushing high-traffic scenarios
Typical use caseTesting, development, controlled setupsBig or complex production sitesHigh-traffic sites, load balancing, reverse proxying

For development and testing, a Python web server is tough to beat because it asks almost nothing of you to get going. When you’re dealing with actual traffic in production, though, a dedicated server process with a reverse proxy in front does the job better — which is what the second half of this guide sets up.

Part 1: Run a Python Web Server Locally (for Testing)

Before anything goes live, you test it locally. This section covers everything you need to run a Python web server on localhost: from the built-in python -m http.server one-liner that needs zero installation, to a full Flask app with templates and a database. All of it runs on your own machine — python local server, python localhost, localhost python web server — and none of it is ready for production traffic yet.

Prerequisites

A few basics to have ready before starting a local Python web server:

Python installed. Most Linux distributions already have Python 3.x, but it’s worth checking. On Debian-based systems like Ubuntu, you can install or update with:

sudo apt update sudo apt install python3

A text editor or IDE. Visual Studio Code, PyCharm, and Sublime Text all work well for writing and editing Python scripts.

Some Python knowledge. You don’t need to be an expert, just comfortable enough to write and run a basic script.

Command line familiarity. You’ll be typing commands and moving between directories. If the terminal feels brand new, spend some time getting comfortable there first.

Basic HTTP understanding. Knowing how requests and responses work makes everything click faster, though you can follow along without it.

The Built-in One-Liner: python -m http.server

Python’s standard library ships with a ready-made HTTP server — no install needed just to serve files from a folder. Open any directory and run python3 -m http.server to start a Python HTTP server on port 8000 by default. It serves every file in that folder to anyone who opens http://localhost:8000 in a browser.

Want a different port? Tack the number on:

python3 -m http.server 8080

Want to lock it down to just your machine instead of your whole local network? Bind it to the loopback address:

python3 -m http.server 8080 --bind 127.0.0.1

This is the fastest way to run a Python server when you just need to share a folder, preview a static site, or see how a browser handles some files. No routing, no templates, no database. Once your project needs any of that, it’s time to grab a framework. It also directly answers how to run python server and run python server — just one command, zero configuration.

Frameworks: Flask vs Django

Flask and Django are the two Python frameworks most developers reach for, and they’re basically opposite ends of the same spectrum.

Flask keeps things light and extensible, giving you just the essentials to build a web application. It works fine as a single-file app for something quick, and scales up cleanly when that project grows. It’s a common starting point for learning Python frameworks in general.

Django takes the “batteries-included” approach. It ships with its own ORM so you can handle database work without raw SQL, comes with a built-in admin panel, and fits larger applications that want more structure right out of the gate.

Two other names worth knowing: Pyramid, which sits between Flask’s minimalism and Django’s completeness, and Tornado, built around async handling and long-lived connections like WebSockets.

Which one to pick depends on project size, how much structure you want handed to you, and what you’re already familiar with.

Set Up a Local Flask Server (step-by-step)

With Python installed, getting a local Flask server running takes just a few steps.

Install Flask using pip, Python’s package manager:

pip install Flask

Create a project directory and jump into it:

mkdir my_project cd my_project

Set up a virtual environment so this project’s dependencies stay separate from everything else:

python3 -m venv venv

Activate it. macOS or Linux:

source venv/bin/activate

Windows:

.\venv\Scripts\activate

Once the virtual environment is active, install Flask if you skipped that earlier, then create a file called app.py with a minimal application:

from flask import Flask app = Flask(__name__)   @app.route('/') def hello_world():     return 'Hello, World!'   if __name__ == '__main__':     app.run(debug=True)

Run it from your project directory:

python app.py

Pop open a browser and hit http://localhost:5000. If “Hello, World!” shows up, your local Python web server is running.

Develop Your First Web App (static + dynamic)

Real web apps usually mix two types of content: static files that never change (images, CSS, JavaScript), and dynamic content the server generates fresh for each request.

For static files, Flask looks for a folder named static in your project directory by default. Drop your files there and reference them in HTML like /static/logo.png for an image called logo.png.

For dynamic content, Flask uses templates. Create a folder called templates, put an HTML file like index.html inside it, and render it from a route using Flask’s render_template function:

from flask import Flask, render_template   app = Flask(__name__)   @app.route('/') def home():     return render_template('index.html')

Small example pulling both together. This Flask app returns a greeting with the current time, generated fresh every request (a classic python web server example):

from flask import Flask from datetime import datetime   app = Flask(__name__)   @app.route('/') def home():     current_time = datetime.now().strftime('%H:%M:%S')     return f'Hello, the current time is {current_time}'   if __name__ == '__main__':     app.run(debug=True)

Run python app.py, then visit http://localhost:5000 to watch the greeting update with the current time on each refresh. From here, add more routes, or swap the plain text for a rendered HTML template.

Database Integration

Most real applications need somewhere to store data, and Python plays nicely with everything from lightweight file-based databases to full client-server systems.

Picking a database: SQLite is easiest for development since it doesn’t need a separate server and keeps everything in one file. For production or anything that needs to scale, PostgreSQL or MySQL are more common picks.

Using an ORM: An Object-Relational Mapping tool like SQLAlchemy or Django’s built-in ORM lets you work with database records as Python objects instead of writing raw SQL. This keeps your code portable and easier to maintain.

Hooking up a database: install whatever driver your database needs (psycopg2 for PostgreSQL, for example), then point your ORM at it. In Flask with SQLAlchemy:

from flask_sqlalchemy import SQLAlchemy app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://username:password@localhost/mydatabase' db = SQLAlchemy(app)

Quick rule of thumb: SQLite for development and quick prototypes, PostgreSQL when data integrity and scalability matter, MySQL when you want reliability and broad tooling support.

Local Testing Troubleshooting

A few issues come up repeatedly when running a Python web server locally. Here they are framed as questions:

Why won’t my server start? Check that Python and Flask are installed correctly, and scan app.py for syntax errors. Even a tiny typo stops the server cold.

What if a port is already in use? Change which port your app listens on by passing it to run, like app.run(debug=True, port=5001).

Why aren’t my static files loading? Make sure the files are inside the static folder and your HTML is pointing to the right path.

Why is Flask saying a template wasn’t found? Confirm the HTML file is inside the templates folder and the filename you pass to render_template matches exactly, extension and all.

Most of these clear up fast once you actually read the error message Flask prints in the terminal. It usually points right at the problem.

Part 2: Host Your Python App in Production (on a VPS)

The built-in Python server and Flask’s dev server are for testing only — they were never designed for real traffic, concurrent users, or staying online unattended. For production python hosting, you need a WSGI server running your app behind an NGINX reverse proxy, with systemd keeping everything alive through reboots and crashes. That stack is what this section sets up.

Why the Dev Server Isn’t for Production

Flask’s app.run() and Django’s runserver are single-threaded by default, never hardened for public exposure, and choke under concurrent connections. They also auto-reload code and print detailed error pages — both handy while debugging, both terrible ideas on the open internet.

Production Python hosting splits running the app from exposing it to the web. A dedicated WSGI server handles the app; a reverse proxy handles the public-facing side.

Run Your App with a WSGI Server (Gunicorn / uWSGI)

WSGI is the standard interface letting a Python web application talk to a server process built for production load. Gunicorn gets picked most often for Flask and Django; uWSGI is a solid alternative with more configuration options if you need them.

Install Gunicorn inside your project’s virtual environment:

pip install gunicorn

For a Flask app where app.py defines an object called app, run it bound to localhost only:

gunicorn --workers 3 --bind 127.0.0.1:8000 app:app

For a Django project, point Gunicorn at the project’s WSGI module instead:

gunicorn --workers 3 --bind 127.0.0.1:8000 myproject.wsgi:application

Worker count is a starting point. A common guideline is two to four workers per CPU core, adjusted based on how memory- or CPU-heavy each request gets. Binding to 127.0.0.1 instead of 0.0.0.0 keeps Gunicorn unreachable from outside the server, since NGINX will be the only thing facing public traffic.

Put NGINX in Front as a Reverse Proxy

With Gunicorn running on localhost, NGINX sits in front — accepting public requests on ports 80 and 443 and forwarding them internally. A minimal reverse proxy config for a single Python app looks like this:

server {     listen 80;     server_name example.com;       location / {         proxy_pass http://127.0.0.1:8000;         proxy_set_header Host $host;         proxy_set_header X-Real-IP $remote_addr;         proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;         proxy_set_header X-Forwarded-Proto $scheme;     } }

Save this as a site config under /etc/nginx/sites-available/, symlink it into sites-enabled, then test and reload NGINX:

sudo nginx -t sudo systemctl reload nginx

Installing and configuring NGINX from scratch on Ubuntu is covered in detail in our guide to setting up an NGINX web server. This section focuses only on the proxy block your Python app needs.

Run It as a systemd Service

Running Gunicorn by hand in a terminal works until you close that session or reboot the server. systemd fixes that by starting Gunicorn automatically on boot and restarting it if it crashes.

Unit file at /etc/systemd/system/gunicorn.service might look like:

[Unit] Description=Gunicorn instance for myapp After=network.target   [Service] User=www-data Group=www-data WorkingDirectory=/var/www/myapp ExecStart=/var/www/myapp/venv/bin/gunicorn --workers 3 --bind 127.0.0.1:8000 app:app Restart=always   [Install] WantedBy=multi-user.target

Then enable and start it:

sudo systemctl daemon-reload sudo systemctl enable gunicorn sudo systemctl start gunicorn sudo systemctl status gunicorn

Once this service is enabled, the app comes back up on its own after a reboot or crash — no need to SSH in and restart manually.

Firewall, Domain & HTTPS

Three things stand between a working Gunicorn and NGINX setup and a publicly reachable, secure Python app.

Firewall rules. Open the ports NGINX needs, nothing else. With UFW:

sudo ufw allow 80/tcp sudo ufw allow 443/tcp sudo ufw enable

Our firewall guide covers UFW config and common rule sets in more detail.

A domain. Point an A record for your domain or subdomain at your VPS’s public IP through your DNS provider, then give it a few minutes to propagate before testing.

HTTPS. Once the domain resolves, Let’s Encrypt hands out free TLS certificates through Certbot. On a server already running NGINX:

sudo certbot --nginx -d example.com

Certbot edits the NGINX config automatically to handle the certificate and redirect HTTP to HTTPS. For the full certificate setup and renewal process, our SSL and Let’s Encrypt guide walks through it end to end.

Local Server vs Production Hosting (quick comparison)

Both halves of this guide connect here. The dev server gets you running in seconds; the production stack handles everything the dev server can’t:

AspectDev Server (Flask / python -m http.server)Gunicorn + NGINX (production)
ConcurrencySingle-threaded, one request at a timeMultiple workers, concurrent traffic
SecurityNot hardened for public useBuilt for internet-facing traffic behind a proxy
TLS / HTTPSNo native supportHandled by NGINX with Let’s Encrypt
Starts on bootNo — manual start every timeYes — managed by systemd
Best forLocal development and testingLive traffic on a domain

Alternatives (Node.js, PHP, Docker + NGINX)

Python isn’t the only path to local web development or production hosting — worth knowing what else is out there.

Node.js with Express. JavaScript on both client and server makes sense if you already know the language, and Express keeps the framework minimal. The trade-off is that callback-heavy async code can be steeper to learn, and bigger apps often need extra tooling to stay organised.

PHP. Still widely used, with tons of documentation, a big community, and an easy entry point for small projects since it embeds right into HTML. Without a modern framework on top it can get messy quickly, and the language has some long-standing quirks.

Docker + NGINX. Packaging an app in Docker makes the environment portable and consistent across machines, and NGINX handles static content and load balancing well. The trade-off is a real learning curve around containerisation, and NGINX config takes some getting used to for beginners.

Compared with these, Python holds up well for readability and the sheer range of libraries and frameworks available — part of why it stays popular for both local development and, with the right setup, production hosting.

Why Host Your Python App on a Contabo VPS

Everything in Part 2 — the WSGI server, NGINX reverse proxy, and systemd service — needs a machine with full root access to set up properly. That’s exactly what a VPS provides, and it’s where our lineup fits cleanly.

Full root access means you can install Gunicorn, configure NGINX, write systemd unit files, and manage UFW firewall rules without restriction. You’re not locked into a shared hosting panel that hides the stack from you.

Contabo’s Core VPS line gives you a generous RAM-per-euro ratio for smaller Flask or Django apps that don’t need much CPU headroom. For database-backed apps — anything running PostgreSQL or MySQL alongside your Python app — the NVMe Plus range cuts disk I/O wait significantly. Both lines are available across EU data centres, which matters if your users are in Europe and GDPR data-locality requirements apply.

If you’re weighing free Python hosting options against a paid VPS: free tiers on shared platforms come with cold starts, memory caps, and no control over the underlying stack. A Contabo entry-level VPS costs little more per month, gives you the full Gunicorn + NGINX + systemd setup described in this guide, and stays running without sleeping on inactivity.

FAQ: Python Web Servers & Hosting

How do I start a local Python web server?

The quickest way is python3 -m http.server in any directory — no install needed, files served at http://localhost:8000 immediately. For a full web app with routing, install Flask with pip install Flask, create a minimal app.py with at least one route, and run python app.py to start on localhost:5000.

What is python -m http.server?

It’s a built-in Python module that starts a simple HTTP file server in the current directory on port 8000 by default. No installation is required — run python3 -m http.server in any folder. Ideal for quickly previewing static HTML files or sharing a local directory; it has no routing, templating, or database support.

Can I use the Flask development server in production?

No. Flask’s app.run() is single-threaded, not hardened for public traffic, and exposes debug information that is a security risk in production. For real traffic, use Gunicorn or uWSGI as your WSGI server, with NGINX in front as a reverse proxy. Flask’s own documentation explicitly warns against using the dev server in production.

How do I host a Python web app in production?

Deploy your app on a VPS, run it with Gunicorn bound to localhost, and put NGINX in front as a reverse proxy on ports 80 and 443. Use Certbot for a free HTTPS certificate from Let’s Encrypt, and manage Gunicorn with a systemd service so it starts automatically on boot and restarts after crashes.

Do I need Gunicorn and NGINX to host Python?

Gunicorn (or uWSGI) replaces Flask’s dev server with a proper multi-worker WSGI process capable of handling concurrent requests. NGINX is not strictly required but is strongly recommended: it handles static files efficiently, terminates TLS, and protects Gunicorn from direct public exposure. Together they form the standard Python production stack.

What do I need to host a Python website on a VPS?

You need: a VPS with root access, Python and pip installed, your app deployed inside a virtual environment, Gunicorn as the WSGI server, NGINX as a reverse proxy, a domain with an A record pointing to your VPS IP, and a free TLS certificate from Let’s Encrypt via Certbot for HTTPS.

Scroll to Top