Caddy is a powerful, enterprise-ready, open source web server with automatic HTTPS written in Go.
Caddy Snake is a Caddy plugin that lets you run Python web apps directly inside Caddy — no reverse proxy needed.
It embeds Python via the C API, so your WSGI or ASGI application runs in the same process as Caddy. This means less overhead, simpler deployments, and automatic HTTPS out of the box.
To make it easier to get started you can also grab one of the precompiled binaries that comes with Caddy and Python, or one of the Docker images.
Works with Flask, Django, FastAPI, and any other WSGI/ASGI framework.
- WSGI & ASGI support — serve any Python web framework (Flask, Django, FastAPI, Starlette, etc.)
- Multi-worker — process-based (default) or thread-based workers for concurrent request handling
- Auto-reload — watches
.pyfiles and hot-reloads your app on changes during development - Dynamic module loading — use Caddy placeholders to load different apps per subdomain or route
- Virtual environment support — point to a
venvand dependencies are available automatically - WebSocket support — full WebSocket handling for ASGI apps
- ASGI lifespan events — optional startup/shutdown lifecycle hooks
- Static file serving — built-in static file support via the CLI
- Pre-built binaries — download and run with Python embedded, no compilation required
- Docker images — ready-to-use images for Python 3.10 through 3.14
- Cross-platform — Linux, macOS, and Windows
The easiest way to get started on Linux is to download a pre-compiled Caddy binary from the latest release. It comes with Python embedded — no system Python required.
# Start a WSGI server
./caddy python-server --server-type wsgi --app main:app
# Start an ASGI server
./caddy python-server --server-type asgi --app main:appThis starts a server on port 9080 serving your app. See ./caddy python-server --help for all options:
--server-type wsgi|asgi Required. Type of Python app
--app <module:var> Required. Python module and app variable (e.g. main:app)
--domain <example.com> Enable HTTPS with automatic certificates
--listen <addr> Custom listen address (default: :9080)
--workers <count> Number of worker processes (default: CPU count)
--workers-runtime <type> Worker type: process or thread
--static-path <path> Serve a static files directory
--static-route <route> Route prefix for static files (default: /static)
--debug Enable debug logging
--access-logs Enable access logs
--autoreload Watch .py files and reload on changes
CGO_ENABLED=1 xcaddy build --with github.com/mliezun/caddy-snake- Python >= 3.10 + dev files
- C compiler and build tools
- Go >= 1.25 and xcaddy
Install on Ubuntu 24.04:
sudo apt-get install python3-dev build-essential pkg-config golang
go install github.com/caddyserver/xcaddy/cmd/xcaddy@latestDocker images are available with Python 3.10, 3.11, 3.12, 3.13, and 3.14:
FROM mliezun/caddy-snake:latest-py3.13
WORKDIR /app
COPY . /app
CMD ["caddy", "run", "--config", "/app/Caddyfile"]Images are published to both registries:
main.py
from flask import Flask
app = Flask(__name__)
@app.route("/hello-world")
def hello():
return "Hello world!"Caddyfile
http://localhost:9080 {
route {
python {
module_wsgi "main:app"
}
}
}pip install Flask
./caddy run --config Caddyfilecurl http://localhost:9080/hello-world
# Hello world!main.py
from fastapi import FastAPI
app = FastAPI()
@app.get("/hello-world")
def hello():
return "Hello world!"Caddyfile
http://localhost:9080 {
route {
python {
module_asgi "main:app"
lifespan on
}
}
}pip install fastapi
./caddy run --config Caddyfilecurl http://localhost:9080/hello-world
# Hello world!The python directive supports the following subdirectives:
python {
module_wsgi "module:variable" # WSGI app to serve (e.g. "main:app")
module_asgi "module:variable" # ASGI app to serve (e.g. "main:app")
venv "/path/to/venv" # Virtual environment path
working_dir "/path/to/app" # Working directory for module resolution
workers 4 # Number of worker processes (default: CPU count)
workers_runtime process|thread # Worker type (default: process on Linux/macOS, thread on Windows)
lifespan on|off # ASGI lifespan events (default: off)
autoreload # Watch .py files and reload on changes
}You must specify either module_wsgi or module_asgi (not both).
The Python module and WSGI application variable to import, in "module:variable" format.
The Python module and ASGI application variable to import, in "module:variable" format.
Path to a Python virtual environment. Behind the scenes, this appends venv/lib/python3.x/site-packages to sys.path so installed packages are available to your app.
Note: The venv packages are added to the global
sys.path, which means all Python apps served by Caddy share the same packages.
Sets the working directory for Python module resolution and relative paths. This is important when deploying with systemd (which defaults to /) or in monorepo/container setups where your app lives in a subdirectory.
python {
module_wsgi "main:app"
venv "/var/www/myapp/venv"
working_dir "/var/www/myapp"
}Number of worker processes to spawn. Defaults to the number of CPUs (GOMAXPROCS). When using workers_runtime thread, only 1 worker is used regardless of this setting.
Controls how requests are handled:
process(default on Linux/macOS) — spawns separate worker processes, each with its own Python interpreter. Best for CPU-bound workloads and production use.thread— runs Python in the main Caddy process using a single interpreter. Required on Windows. Needed forautoreload.
Enables ASGI lifespan events (startup and shutdown). Only applies to ASGI apps. Defaults to off.
Watches the working directory for .py file changes and automatically reloads the Python app. Useful during development. Requires workers_runtime thread.
Changes are debounced (500ms) to handle rapid edits.
python {
module_wsgi "main:app"
workers_runtime thread
autoreload
}You can use Caddy placeholders in module_wsgi, module_asgi, working_dir, and venv to dynamically load different Python apps based on the request.
This is useful for multi-tenant setups where each subdomain or route serves a different application:
*.example.com:9080 {
route /* {
python {
module_asgi "{http.request.host.labels.2}:app"
working_dir "{http.request.host.labels.2}/"
workers_runtime thread
}
}
}In this example, a request to app1.example.com loads the app from the app1/ directory, app2.example.com loads from app2/, and so on. Apps are lazily created on first request and cached for subsequent requests.
There are two approaches for hot reloading during development:
Add the autoreload directive to your Caddyfile. This watches for .py file changes and reloads the app in-place without restarting Caddy:
python {
module_wsgi "main:app"
workers_runtime thread
autoreload
}Note:
autoreloadrequiresworkers_runtime threadsince it reloads the Python module within the same interpreter.
You can also use watchmedo to restart Caddy on file changes:
# Install on Debian/Ubuntu
sudo apt-get install python3-watchdog
watchmedo auto-restart -d . -p "*.py" --recursive \
-- caddy run --config CaddyfileNote that this restarts the entire Caddy process on changes.
There's a template file in the project: builder.Dockerfile. It supports build arguments to configure which Python or Go version to use.
# Build the Docker image
docker build -f builder.Dockerfile --build-arg PY_VERSION=3.13 -t caddy-snake-builder .
# Extract the caddy binary to your current directory
docker run --rm -v $(pwd):/output caddy-snake-builderMake sure to match the Python version with your target environment.
Here's how it compares to traditional setups using a simple JSON "Hello, World!" endpoint:
| Configuration | Requests/sec | Avg Latency (ms) | P99 Latency (ms) |
|---|---|---|---|
| Flask + Gunicorn + Caddy | 1,815 | 54.92 | 66.85 |
| Flask + Caddy Snake | 2,008 | 49.67 | 54.90 |
| FastAPI + Uvicorn + Caddy | 3,601 | 27.70 | 261.59 |
| FastAPI + Caddy Snake | 3,361 | 29.72 | 45.63 |
Benchmarked on a Scaleway POP2-2C-8G instance (2 vCPUs, 8GB RAM) with hey — 100 concurrent connections, 10s duration, Python 3.13, Go 1.26, thread workers. See benchmarks/ for methodology and how to reproduce.
| Platform | Workers runtime | Notes |
|---|---|---|
| Linux (x86_64) | process, thread | Primary platform, full support |
| Linux (arm64) | process, thread | Full support |
| macOS | process, thread | Full support |
| Windows | thread only | Process workers not supported on Windows |
Python versions: 3.10, 3.11, 3.12, 3.13, 3.13-nogil (free-threaded), 3.14
