Learning objectives
After reading this article you will be able to:
- Pick the right production server for each framework
- Prepare and start a FastAPI, Flask or Django app outside your computer
- Fix the most common errors in a newly published Python app
All three frameworks ship a built-in server that starts with one command, and that is where the most common mistake lives: taking that server to production. It is built for development. To publish, the app needs an application server (Uvicorn or Gunicorn) that listens on the right interface and reads its configuration from the environment.
This guide shows the minimum for each framework, the matching production command and the settings Django requires. The general reasoning for publishing an API is in how to host an API.
Which production server does each framework use?#
The difference is the interface between the framework and the server:
| Framework | Interface | Production server | Typical command |
|---|---|---|---|
| FastAPI | ASGI | Uvicorn (comes with fastapi[standard]) |
fastapi run or uvicorn main:app |
| Flask | WSGI | Gunicorn | gunicorn 'app:app' |
| Django | WSGI (or ASGI) | Gunicorn | gunicorn mysite.wsgi |
Gunicorn does not run on Windows (only under WSL). Production servers are usually Linux, so this only affects local testing.
How do you host a FastAPI app?#
Install it with the standard extras, which include Uvicorn:
pip install "fastapi[standard]"main.py:
from fastapi import FastAPI
app = FastAPI()
@app.get("/health")
def health():
return {"status": "ok"}In production, without --reload (the FastAPI docs warn that it uses more resources and is less stable):
uvicorn main:app --host 0.0.0.0 --port "${PORT:-8000}" --workers 2main:app means "the app object in the file main.py". Each worker is a separate process, and startup code runs once per worker. fastapi run main.py is the short alternative: it also listens on 0.0.0.0, on port 8000 by default.
How do you host a Flask app?#
pip install flask gunicornapp.py:
import os
from flask import Flask
app = Flask(__name__)
app.config["SECRET_KEY"] = os.environ["SECRET_KEY"]
@app.get("/health")
def health():
return {"status": "ok"}Start it with Gunicorn:
gunicorn -w 4 -b "0.0.0.0:${PORT:-8000}" 'app:app'The app:app argument follows the {module}:{variable} format. With the application factory pattern, use 'app:create_app()'. -w sets the number of processes, and the Flask docs suggest CPU * 2 as a starting point. Without -b, Gunicorn listens on 0.0.0.0 on the port in the PORT variable if it is set; without PORT, it listens only on 127.0.0.1:8000, and the app looks down. Passing -b makes the address explicit either way.
How do you host a Django app?#
Django needs more setup: with DEBUG on, it exposes code and configuration, and in production nobody serves static files for you.
pip install django gunicorn whitenoiseIn settings.py, read from the environment whatever differs between development and production:
import os
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent
SECRET_KEY = os.environ["SECRET_KEY"]
DEBUG = os.environ.get("DEBUG") == "1"
ALLOWED_HOSTS = os.environ["ALLOWED_HOSTS"].split(",")
CSRF_TRUSTED_ORIGINS = [
o for o in os.environ.get("CSRF_TRUSTED_ORIGINS", "").split(",") if o
]
STATIC_URL = "static/"
STATIC_ROOT = BASE_DIR / "staticfiles"
STORAGES = {
"default": {"BACKEND": "django.core.files.storage.FileSystemStorage"},
"staticfiles": {
"BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage"
},
}
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"whitenoise.middleware.WhiteNoiseMiddleware", # right after SecurityMiddleware
# ... other middleware
]With ALLOWED_HOSTS=myapp.example.com and CSRF_TRUSTED_ORIGINS=https://myapp.example.com, the public domain is allowed. CSRF_TRUSTED_ORIGINS includes the scheme (https://) and applies to POST requests coming from another origin.
Before starting, collect the static files and run migrations:
python manage.py collectstatic --noinput
python manage.py migrate
gunicorn mysite.wsgi -b "0.0.0.0:${PORT:-8000}"WhiteNoise serves the files in STATIC_ROOT straight from the Python process, so you do not need a separate web server for them. Finally, run python manage.py check --deploy with your production settings: it points out what is missing.
If the host's proxy terminates HTTPS and forwards plain HTTP to Django, request.is_secure() returns False. The fix is SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https"), but the Django docs only allow it if the proxy strips any X-Forwarded-Proto header sent by the client and sets it only on requests that arrived over HTTPS. Without that guarantee, keep it at None.
How do you know the deploy worked?#
Test from outside the server, using the public address:
curl -i https://myapp.example.com/healthYou expect HTTP/2 200 and the JSON {"status":"ok"}. In Django, add a simple view at /health or open the home page. If the response is different, read the process logs: the Python traceback shows the exact line.
What are the most common errors?#
The connection is refused or times out. The server listens on 127.0.0.1 or on a port different from the exposed one. Pass --host 0.0.0.0 (Uvicorn) or -b 0.0.0.0:PORT (Gunicorn).
Django returns Bad Request (400). The domain is not in ALLOWED_HOSTS. With DEBUG = False and a list that lacks the domain (or is empty), Django answers every request with 400.
A Django form fails with 403 (CSRF verification failed). The site's origin is not in CSRF_TRUSTED_ORIGINS, or Django does not know the original connection was HTTPS. Check the list and the proxy header.
The Django admin CSS does not load. Either collectstatic was skipped or WhiteNoise is missing from MIDDLEWARE. Run collectstatic on every deploy.
KeyError: 'SECRET_KEY'. The environment variable was not set on the server. The failure is on purpose: better not to start than to start without a secret. See what environment variables are.
ModuleNotFoundError. A dependency is missing from requirements.txt. Generate the file with pip freeze > requirements.txt inside the project's virtual environment.
The app restarts by itself or gets slow. Too many workers for too little RAM: each one is a process with its own copy of the app. Lower --workers or raise the memory.
What else matters before going public?#
- Database: a local SQLite file next to the app is fragile with several workers and when the server's disk is replaced. For real use, prefer PostgreSQL or MySQL; see how to host a database.
- Migrations: run
migrate(Django) or your own migration tool on every deploy, before sending traffic to the new version. - CORS: in FastAPI, use
CORSMiddlewarewith exact origins; in Flask and Django, theflask-corsanddjango-cors-headerspackages. - Custom domain: point the domain to your app and update
ALLOWED_HOSTSat the same time.
How to do this on Square Cloud#
The production commands in this guide become your application's start command on Square Cloud. You upload the project as a ZIP in the dashboard, with the CLI, through GitHub or with the VS Code extension; at start, the platform runs pip install -r requirements.txt and sets the PORT variable. The squarecloud.app file is optional: the dashboard generates it on deploy. In the deploy form's start command field, use your framework's server:
- FastAPI:
uvicorn main:app --host 0.0.0.0 --port $PORT --workers 2 - Flask:
gunicorn -w 2 -b "0.0.0.0:$PORT" 'app:app' - Django:
python manage.py collectstatic --noinput && python manage.py migrate && gunicorn mysite.wsgi -b "0.0.0.0:$PORT"
Since there is no build step, Django collects its static files and applies migrations right there, on every start. Every plan gives a web application a *.squareweb.app subdomain with automatic TLS, and that host is what goes into Django's variables: ALLOWED_HOSTS=myapp.squareweb.app and CSRF_TRUSTED_ORIGINS=https://myapp.squareweb.app, next to SECRET_KEY. Variables live in the dashboard, already exist during the install and apply on the next restart, with no rebuild.
Each worker takes memory, and a web application uses at least 512 MB of the plan's RAM; Hobby starts at R$12.49 per month with 1 GB. If the workers run out of memory, the app stops until you raise its RAM or lower the worker count. Automatic restart, on by default for dashboard deploys, only covers processes that exit with an error. The disk persists across deploys, but SQLite is still fragile with several workers: PostgreSQL and MySQL hosting starts at Standard (from R$44.99 per month), in the same datacenter as your applications.
See the plans on API hosting and the runtime on Python.
API hosting
APIs in always-on containers, with no cold starts
FAQs
Can I use Flask's development server or Django's runserver in production?
No. They are for development, not for real load or security. In production, use Gunicorn (Flask and Django) or Uvicorn (FastAPI).
How many workers should I use?
Start small and adjust from real usage. The Flask docs suggest starting at CPU * 2 with Gunicorn, but each worker is a process with its own memory, so your RAM limit matters too.
Why does Django return 400 (Bad Request) after deploy?
With DEBUG off, Django only accepts the hosts listed in ALLOWED_HOSTS. Add the host that appears in the URL to the list.
Do I need Nginx in front of Gunicorn?
On your own server (a VPS), the Flask docs recommend a reverse proxy in front of Gunicorn. On platforms that already provide HTTPS and routing, that layer is already in place.
More in APIs
See all 15 articles- How to host an APIHosting an API means running its HTTP server around the clock at a public address with HTTPS. Step-by-step with Node.js and Python, testing and common errors.
- How to host a Node.js appTo host a Node.js app, prepare package.json, read the port and secrets from the environment, pick an LTS version and run it with node. Step-by-step guide.
- What is a cron job? Syntax and examplesA cron job is a task that runs on its own at times set by a five-field expression. Learn the syntax, examples, common mistakes and how to schedule jobs in Node.js.
- Production checklist for your APIA production checklist for your API: environment variables, health check, graceful shutdown, standard errors, authentication, rate limiting, CORS and logs.