How to Setup Gunicorn on FastAPI for Concurrent Invocation

## Pendahuluan FastAPI adalah framework Python modern yang sangat cepat untuk membangun API. Namun, secara default, FastAPI development server (uvicorn) hanya menjalankan satu worker process, yang be...

By Ruby Abdullah · · tutorial
FastAPIGunicornPythonConcurrencyProduction

Introduction

FastAPI is a modern, fast Python framework for building APIs. However, by default, the FastAPI development server (uvicorn) only runs a single worker process, which means it can only handle one request at a time. For production and handling concurrent requests, we need Gunicorn as a process manager.

Gunicorn (Green Unicorn) is an HTTP server for Python that can run multiple worker processes, allowing our FastAPI application to handle many requests simultaneously.

Why Gunicorn?

Advantages of using Gunicorn:
  • Multiple worker processes for concurrent request handling
  • Automatic load balancing between workers
  • Graceful restart without downtime
  • Production-ready and battle-tested
  • Compatible with uvicorn workers for async support

Installation

First, install the required dependencies:

pip install fastapi uvicorn gunicorn

Or add to requirements.txt:

fastapi==0.109.0

uvicorn[standard]==0.27.0

gunicorn==21.2.0

Then install:

pip install -r requirements.txt

Creating a Simple FastAPI Application

Create a main.py file:

from fastapi import FastAPI

import time

import os

app = FastAPI()

@app.get("/")

async def root():

return {

"message": "Hello World",

"workerpid": os.getpid()

}

@app.get("/slow")

async def slowendpoint():

# Simulate time-consuming operation

time.sleep(5)

return {

"message": "This took 5 seconds",

"workerpid": os.getpid()

}

@app.get("/health")

async def healthcheck():

return {"status": "healthy"}

Setting Up Gunicorn with Uvicorn Workers

Gunicorn itself is a synchronous server. To get async benefits from FastAPI, we use Uvicorn workers.

Running from Command Line

gunicorn main:app --workers 4 --worker-class uvicorn.workers.UvicornWorker --bind 0.0.0.0:8000

Parameter explanation:
  • main:app - module:application (main.py with app variable)
  • --workers 4 - number of worker processes
  • --worker-class uvicorn.workers.UvicornWorker - use async uvicorn workers
  • --bind 0.0.0.0:8000 - host and port

Creating a Configuration File

Create a gunicornconf.py file for more structured configuration:

import multiprocessing

import os

Server Socket

bind = "0.0.0.0:8000"

backlog = 2048

Worker Processes

workers = int(os.getenv("WORKERS", multiprocessing.cpucount() 2 + 1))

workerclass = "uvicorn.workers.UvicornWorker"

workerconnections = 1000

maxrequests = 10000

maxrequestsjitter = 1000

timeout = 120

keepalive = 5

Logging

accesslog = "-"

errorlog = "-"

loglevel = "info"

accesslogformat = '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s" %(D)s'

Process Naming

procname = "fastapiapp"

Server Mechanics

daemon = False

pidfile = None

user = None

group = None

tmpuploaddir = None

Graceful Timeout

gracefultimeout = 30

Run with config file:

gunicorn main:app -c gunicornconf.py

Determining Optimal Number of Workers

General formula for determining number of workers:

workers = (2  CPUCORES) + 1

Examples:
  • 2 CPU cores = 5 workers
  • 4 CPU cores = 9 workers
  • 8 CPU cores = 17 workers

Tips:
  • For CPU-bound tasks: use the formula above
  • For I/O-bound tasks: can use more workers
  • Monitor memory usage, don't run out of RAM
  • Start with the standard formula, then adjust based on monitoring

Testing Concurrent Requests

Test whether concurrent invocation works correctly.

Using Python

Create a testconcurrent.py file:

import asyncio

import aiohttp

import time

async def makerequest(session, url, requestid):

start = time.time()

async with session.get(url) as response:

data = await response.json()

duration = time.time() - start

print(f"Request {requestid}: PID={data.get('workerpid')} Duration={duration:.2f}s")

return data

async def testconcurrent():

url = "http://localhost:8000/slow"

async with aiohttp.ClientSession() as session:

tasks = [makerequest(session, url, i) for i in range(10)]

starttime = time.time()

results = await asyncio.gather(*tasks)

totaltime = time.time() - starttime

print(f"\nTotal time for 10 concurrent requests: {totaltime:.2f}s")

print(f"Without concurrency would take: ~50s")

# Check different PIDs

pids = set(r.get('workerpid') for r in results)

print(f"\nRequests handled by {len(pids)} different workers")

print(f"Worker PIDs: {pids}")

if name == "main":

asyncio.run(testconcurrent())

Run the test:

pip install aiohttp

python testconcurrent.py

Expected output:
Request 0: PID=12345 Duration=5.02s

Request 1: PID=12346 Duration=5.03s

Request 2: PID=12347 Duration=5.01s

...

Total time for 10 concurrent requests: 5.10s

Without concurrency would take: ~50s

Requests handled by 4 different workers

Worker PIDs: {12345, 12346, 12347, 12348}

Using Apache Bench

ab -n 100 -c 10 http://localhost:8000/

Parameters:
  • -n 100 - total 100 requests
  • -c 10 - 10 concurrent requests

Environment Variables

Create a .env file for configuration:

WORKERS=4

HOST=0.0.0.0

PORT=8000

LOGLEVEL=info

Update gunicornconf.py to read env vars:

import os

from dotenv import loaddotenv

loaddotenv()

bind = f"{os.getenv('HOST', '0.0.0.0')}:{os.getenv('PORT', '8000')}"

workers = int(os.getenv('WORKERS', 4))

loglevel = os.getenv('LOGLEVEL', 'info')

Production Deployment

Using Systemd

Create a /etc/systemd/system/fastapi.service file:

[Unit]

Description=FastAPI Application

After=network.target

[Service]

User=www-data

Group=www-data

WorkingDirectory=/path/to/your/app

Environment="PATH=/path/to/venv/bin"

ExecStart=/path/to/venv/bin/gunicorn main:app -c gunicornconf.py

Restart=always

RestartSec=10

[Install]

WantedBy=multi-user.target

Enable and start the service:

sudo systemctl daemon-reload

sudo systemctl enable fastapi

sudo systemctl start fastapi

sudo systemctl status fastapi

Using Docker

Create a Dockerfile:

FROM python:3.11-slim

WORKDIR /app

COPY requirements.txt .

RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 8000

CMD ["gunicorn", "main:app", "-c", "gunicornconf.py"]

Build and run:

docker build -t fastapi-app .

docker run -p 8000:8000 -e WORKERS=4 fastapi-app

Docker Compose

Create a docker-compose.yml file:

version: '3.8'

services:

api:

build: .

ports:

  • "8000:8000"
environment:

  • WORKERS=4
  • LOGLEVEL=info
restart: unless-stopped

healthcheck:

test: ["CMD", "curl", "-f", "http://localhost:8000/health"]

interval: 30s

timeout: 10s

retries: 3

Run:

docker-compose up -d

Best Practices

1. Worker Management

# gunicornconf.py

Restart workers after handling requests (prevent memory leaks)

maxrequests = 1000

maxrequestsjitter = 50 # Randomize restart to avoid thundering herd

Timeout for long-running requests

timeout = 120

Graceful shutdown

gracefultimeout = 30

2. Health Checks

Implement health check endpoint:

from fastapi import FastAPI, status

from fastapi.responses import JSONResponse

@app.get("/health")

async def healthcheck():

return JSONResponse(

statuscode=status.HTTP200OK,

content={

"status": "healthy",

"workerpid": os.getpid()

}

)

3. Monitoring

Use middleware for tracking:

import time

from fastapi import Request

@app.middleware("http")

async def addprocesstimeheader(request: Request, callnext):

starttime = time.time()

response = await callnext(request)

processtime = time.time() - starttime

response.headers["X-Process-Time"] = str(processtime)

response.headers["X-Worker-PID"] = str(os.getpid())

return response

4. Logging

Setup proper logging:

import logging

from fastapi import FastAPI

logging.basicConfig(

level=logging.INFO,

format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'

)

logger = logging.getLogger(name)

app = FastAPI()

@app.onevent("startup")

async def startupevent():

logger.info(f"Worker started with PID: {os.getpid()}")

@app.onevent("shutdown")

async def shutdownevent():

logger.info(f"Worker shutting down with PID: {os.getpid()}")

Common Issues and Solutions

Issue 1: Workers Timeout

Problem: Workers die because requests take too long Solution: Increase timeout
timeout = 300  # 5 minutes

Issue 2: Memory Leak

Problem: Memory usage keeps growing Solution: Auto-restart workers
maxrequests = 1000

maxrequestsjitter = 100

Issue 3: Too Many Workers

Problem: Server runs out of memory Solution: Reduce number of workers or upgrade RAM
workers = 4  # Start small, monitor, then adjust

Conclusion

Setting up Gunicorn with FastAPI for concurrent invocation includes:

  • Install Gunicorn and Uvicorn workers for async support
  • Determine optimal number of workers based on CPU cores and workload
  • Use configuration file for better management
  • Implement health checks and monitoring for production readiness
  • Test concurrent requests to ensure setup works correctly
  • Follow best practices for performance and reliability
  • With proper setup, your FastAPI application will be ready to handle thousands of concurrent requests efficiently and reliably in a production environment.

    Key Takeaways:
    • Gunicorn + Uvicorn workers = async concurrency support
    • Worker formula: (2 × CPU cores) + 1
    • Always test concurrent behavior before production
    • Monitor and adjust configuration based on real usage
    • Implement proper logging and health checks

    Related Articles

    LitServe Tutorial: Fast and Easy AI Model Serving Framework

    Tutorial LitServe: Framework Serving Model AI yang Cepat dan Mudah Pendahuluan LitServe adalah framework open-source dar...

    Reflex Tutorial: Building Full-Stack Web Apps in Pure Python

    Reflex: Membangun Aplikasi Web Full-Stack dengan Python Murni Reflex memungkinkan Anda membangun aplikasi web lengkap — ...

    SQLModel: Modern Python ORM for Type-Safe AI Applications

    SQLModel: ORM Modern Python untuk Aplikasi AI yang Type-Safe Dalam pengembangan aplikasi AI/ML, pengelolaan data di data...

    Complete FastAPI for Machine Learning Tutorial: Building Production ML APIs

    Tutorial Lengkap FastAPI untuk ML: Build Production ML APIs FastAPI adalah framework web Python modern dengan performa t...