100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Python

Flask with Gunicorn and WSGI

Understand the WSGI standard that powers Flask and how to run Flask applications in production using Gunicorn as an application server.

Testing & DeploymentIntermediate9 min readJul 10, 2026
Analogies

Why Flask's Built-in Server Isn't for Production

app.run() starts Werkzeug's development server, which is single-threaded by default, lacks proper process management, and explicitly warns in its own output that it is not a production WSGI server. It cannot efficiently handle concurrent requests, doesn't restart crashed workers, and has no built-in protection against slow clients tying up the process, all of which matter under real traffic.

🏏

Cricket analogy: A club-level net bowler can deliver balls fine for casual practice but isn't built to bowl 90 overs in a five-day Test match; app.run() is that net bowler, not a production-grade server like Gunicorn.

WSGI: The Interface Between Server and Application

WSGI (Web Server Gateway Interface, PEP 3333) defines a simple callable contract: a WSGI application is any callable that accepts environ (a dict of request data) and start_response (a callback to set status and headers), and returns an iterable of bytes. Flask's app object is itself a WSGI application, which is exactly why Gunicorn can call app:app directly, and why Flask apps are compatible with any WSGI-compliant server without modification.

🏏

Cricket analogy: The Laws of Cricket define a standard interface every ground must follow, so a bowler trained in Mumbai can play a Test at Lord's without relearning the rules, just as WSGI lets any compliant server run any compliant app.

Configuring Gunicorn for a Flask Application

Gunicorn is invoked as gunicorn -w 4 -b 0.0.0.0:8000 myapp:app, where -w 4 spawns four worker processes and myapp:app tells Gunicorn to import the app object from myapp.py. Gunicorn's default sync worker handles one request per worker at a time, which is fine for CPU-bound or fast I/O-bound views, but for apps with slow external calls, gevent or gthread workers let a single worker juggle many concurrent connections.

🏏

Cricket analogy: A T20 franchise fields eleven specialist players simultaneously across different positions rather than relying on one all-rounder to cover everything, the way Gunicorn's four workers handle requests in parallel instead of one process doing it all.

Worker Types and Sizing Guidance

The commonly cited formula for sync workers is (2 x CPU cores) + 1, which balances CPU utilization against the overhead of context switching. For apps that spend most of their time waiting on external I/O (database queries, third-party APIs), gthread workers with a higher --threads count, or async gevent/eventlet workers, allow far more concurrent connections per worker than the sync default without needing dozens of OS processes.

🏏

Cricket analogy: A captain sets a fielding formation based on the bowler and pitch conditions rather than always using the same setup, just as worker count and type should be tuned to the app's CPU vs I/O profile.

bash
# Install
pip install gunicorn

# Run with 4 sync workers on port 8000
gunicorn -w 4 -b 0.0.0.0:8000 myapp:app

# Run with gevent workers for I/O-heavy apps (e.g. lots of DB/API calls)
pip install gevent
gunicorn -k gevent -w 4 --threads 4 -b 0.0.0.0:8000 myapp:create_app()

# gunicorn.conf.py
bind = "0.0.0.0:8000"
workers = 4
worker_class = "gthread"
threads = 4
timeout = 30
accesslog = "-"
errorlog = "-"

myapp:app tells Gunicorn to import the module myapp and use its app attribute. If you use the application factory pattern, point Gunicorn at the factory call instead, e.g. myapp:create_app().

Gunicorn's built-in workers are not designed to serve directly to the public internet efficiently — it should always sit behind a reverse proxy like Nginx that handles TLS termination, static files, and buffering slow clients.

  • Flask's app.run() dev server is single-threaded and explicitly unsuitable for production traffic.
  • WSGI (PEP 3333) is the standard callable interface that lets any WSGI server host any WSGI app, including Flask.
  • Gunicorn is a production WSGI server that manages multiple worker processes for concurrency.
  • Sync workers suit CPU-bound apps; gevent/gthread workers suit I/O-bound apps with many external calls.
  • A common sizing formula for sync workers is (2 x CPU cores) + 1.
  • myapp:app or myapp:create_app() tells Gunicorn where to find the WSGI-callable Flask app.
  • Gunicorn should run behind a reverse proxy like Nginx for TLS, static files, and buffering.

Practice what you learned

Was this page helpful?

Topics covered

#Python#FlaskStudyNotes#WebDevelopment#FlaskWithGunicornAndWSGI#Flask#Gunicorn#WSGI#Built#StudyNotes#SkillVeris