Running Python Apps with Granian

· 6 min read · Updated March 21, 2026 · intermediate
python granian rust async web servers

Granian is an HTTP server for Python written in Rust. It sits between your Python code and the internet—receiving requests, passing them to your application for processing, and sending responses back. The Rust implementation handles the low-level networking parts, giving it speed and efficiency advantages over pure Python servers.

Granian supports three different ways to write your Python web application:

  • ASGI — The Asynchronous Server Gateway Interface, used by FastAPI and Django
  • RSGI — A Granian-specific async interface optimized for Rust
  • WSGI — The traditional Web Server Gateway Interface, used by Flask and older Django versions

This means you can use Granian with whatever web framework you’re already comfortable with.

Installing Granian

Installation is straightforward if you have Python 3.10 or later (3.10+ recommended). Open your terminal and run:

pip install granian

Granian downloads and installs its Rust components automatically.

Optional extras are available for specific features:

# Enable automatic reloading when files change
pip install granian[reload]

# Use faster event loop on Linux
pip install granian[uvloop]

# Enable environment file support
pip install granian[dotenv]

You’ll need a Linux system with io_uring support for the best performance. Most modern Linux distributions have this enabled by default.

Running Your First Server

Create a file called main.py with this code:

# main.py
# This is an ASGI application - a standard way to write async web apps in Python


async def app(scope, receive, send):
    """Every ASGI app is a callable that receives three things."""
    
    # scope contains information about the request (URL, headers, etc.)
    assert scope['type'] == 'http'
    
    # Send the response headers (status 200, content-type text/plain)
    await send({
        'type': 'http.response.start',
        'status': 200,
        'headers': [[b'content-type', b'text/plain']],
    })
    
    # Send the actual response body
    await send({
        'type': 'http.response.body',
        'body': b'Hello from Granian!',
    })

Now run it with Granian:

granian --interface asgi main:app

The --interface asgi part tells Granian you’re using ASGI format. This is important because Granian defaults to RSGI, which has a different callable signature. Open your browser to http://127.0.0.1:8000 and you should see “Hello from Granian!”.

The main:app part means “from the file main.py, import the thing called app”. This follows Python’s standard module:object notation.

Running an RSGI Application

RSGI is Granian’s native interface and offers slightly different performance characteristics. Here’s the same “Hello World” app written in RSGI:

# rsgi_app.py
async def app(scope, proto):
    """RSGI uses a different signature than ASGI."""
    assert scope['type'] == 'http'
    
    # proto provides methods to send responses
    proto.response_str(
        status=200,
        headers=[('content-type', 'text/plain')],
        body="Hello from RSGI!"
    )

Run it with:

granian --interface rsgi rsgi_app:app

Notice you don’t need to specify --interface rsgi since that’s the default.

Using Granian with Web Frameworks

You don’t have to write raw ASGI applications. Most people use a framework like FastAPI or Starlette. Here’s how to run a FastAPI app with Granian:

First, install FastAPI if you haven’t:

pip install fastapi

Create a simple FastAPI app:

# app.py
from fastapi import FastAPI

app = FastAPI()


@app.get("/")
def read_root():
    return {"message": "Hello from FastAPI on Granian!"}


@app.get("/items/{item_id}")
def read_item(item_id: int):
    return {"item_id": item_id, "name": f"Item {item_id}"}

Run it with Granian:

granian --interface asgi app:app

FastAPI handles all the ASGI complexity for you, so your code looks just like normal Python functions.

Understanding Granian’s CLI Options

Granian comes with many command-line options. Here are the ones you’ll use most often:

Choosing a Port and Host

By default, Granian listens on 127.0.0.1:8000. You can change this:

granian --interface asgi --host 0.0.0.0 --port 9000 main:app

Using 0.0.0.0 means your server is accessible from other computers on your network, not just localhost.

Running Multiple Workers

A worker is a separate process that handles requests. More workers can handle more traffic:

granian --interface asgi --workers 4 main:app

This runs 4 separate Python processes, each handling requests. If one crashes, the others keep running.

Enabling WebSockets

If your app uses WebSockets for real-time communication:

granian --interface asgi --ws main:app

WebSockets are enabled by default, but you can disable them with --no-ws if you don’t need them.

Access Logs

To see incoming requests in your terminal:

granian --interface asgi --access-log main:app

This prints each request with its status code and response time to stderr.

HTTPS Support

For production, you’ll often need HTTPS. Granian can handle this directly:

granian --interface asgi \
    --ssl-certificate certificate.crt \
    --ssl-keyfile private.key \
    main:app

The key file must be in PKCS#8 format. If you have a different format, you’ll need to convert it.

Serving Static Files

Granian can serve static files directly without needing another server:

# Serve files from /var/www/static at /static URL path
granian --interface asgi \
    --static-path-route /static \
    --static-path-mount /var/www/static \
    --static-path-expires 3600 \
    main:app
  • --static-path-route sets the URL path (default: /static)
  • --static-path-mount sets the directory to serve files from
  • --static-path-expires sets cache expiry time in seconds (3600 = 1 hour)

This is useful for development or simple deployments. For production, consider using a dedicated static file server like nginx.

Unix Domain Sockets

For connecting to reverse proxies:

granian --interface asgi --uds /var/run/granian.sock main:app

This is common when deploying behind nginx or other reverse proxies.

Using Environment Variables

Instead of typing all options on the command line, you can use environment variables. Create a .env file:

export GRANIAN_HOST=0.0.0.0
export GRANIAN_PORT=8000
export GRANIAN_WORKERS=4
export GRANIAN_LOG_LEVEL=debug

Then run Granian with the --env-files option:

granian --interface asgi --env-files .env main:app

Note: Using --env-files requires the dotenv extra (pip install granian[dotenv]).

This is handy for keeping your deployment configuration separate from your code.

Programmatic Usage (Python API)

Sometimes you want to start Granian from within Python code, perhaps as part of a larger application or test setup:

from granian import Granian


def on_startup():
    """This runs when the server starts."""
    print("Server is starting!")


def on_shutdown():
    """This runs when the server stops."""
    print("Server is stopping!")


server = Granian(
    target="main:app",
    interface="asgi",
    host="127.0.0.1",
    port=8000,
    workers=2,
)

server.on_startup(on_startup)
server.on_shutdown(on_shutdown)

server.serve()

This gives you more control over the server lifecycle.

Common Gotchas

There are a few things that trip up people new to Granian:

The Default Interface is RSGI

Unlike uvicorn or gunicorn, Granian defaults to RSGI (a Granian-specific async interface). Always specify --interface asgi if you’re using FastAPI, Django, or any ASGI framework:

# Wrong - will confuse your ASGI app
granian main:app

# Right - explicitly say you're using ASGI
granian --interface asgi main:app

WSGI and the GIL

If you use WSGI (for Flask), remember that all requests share the same Python process. Heavy computation in one request can slow down others. Consider using ASGI with an async framework for better performance.

Not Compatible with Every Library

Granian doesn’t work with trio or gevent. If a library specifically requires these, you won’t be able to use it with Granian. Most popular libraries work fine, but check compatibility if you’re using something niche.

ASGI Lifespan Not Fully Supported

The ASGI “lifespan” protocol (for startup and shutdown events) isn’t fully implemented yet. If your app relies heavily on lifespan events, you might encounter issues.

When to Use Granian

Granian is a good choice when:

  • You’re already using ASGI frameworks like FastAPI or Starlette
  • You want better performance than pure Python servers
  • You need HTTP/2 support (Granian handles this natively)
  • You’re comfortable on Linux and want to try something faster

It’s less ideal when:

  • You need Windows support (Granian works best on Linux)
  • You’re using WSGI frameworks and don’t need better performance
  • You need extensive debugging tools (Granian is more minimal)

See Also

  • dunder-init — Python’s __init__ method, useful for understanding app factory patterns
  • with-keyword — Context managers for resource handling in web applications
  • dunder-new__new__ method for custom class instantiation