Deploying applications using channels requires a few more steps than a normal Django WSGI application, but you have a couple of options as to how to deploy it and how much of your traffic you wish to route through the channel layers.

Firstly, remember that it’s an entirely optional part of Django. If you leave a project with the default settings (no CHANNEL_LAYERS), it’ll just run and work like a normal WSGI app.

When you want to enable channels in production, you need to do three things:

  • Set up a channel backend
  • Run worker servers
  • Run interface servers

You can set things up in one of two ways; either route all traffic through a HTTP/WebSocket interface server, removing the need to run a WSGI server at all; or, just route WebSockets and long-poll HTTP connections to the interface server, and leave other pages served by a standard WSGI server.

Routing all traffic through the interface server lets you have WebSockets and long-polling coexist in the same URL tree with no configuration; if you split the traffic up, you’ll need to configure a webserver or layer 7 loadbalancer in front of the two servers to route requests to the correct place based on path or domain. Both methods are covered below.

Setting up a channel backend

The first step is to set up a channel backend. If you followed the Getting Started with Channels guide, you will have ended up using the in-memory backend, which is useful for runserver, but as it only works inside the same process, useless for actually running separate worker and interface servers.

Instead, take a look at the list of Channel Layer Types, and choose one that fits your requirements (additionally, you could use a third-party pluggable backend or write your own - that page also explains the interface and rules a backend has to follow).

Typically a channel backend will connect to one or more central servers that serve as the communication layer - for example, the Redis backend connects to a Redis server. All this goes into the CHANNEL_LAYERS setting; here’s an example for a remote Redis server:

    "default": {
        "BACKEND": "asgi_redis.RedisChannelLayer",
        "CONFIG": {
            "hosts": [("redis-server-name", 6379)],
        "ROUTING": "my_project.routing.channel_routing",

To use the Redis backend you have to install it:

pip install -U asgi_redis

Some backends, though, don’t require an extra server, like the IPC backend, which works between processes on the same machine but not over the network (it’s available in the asgi_ipc package):

    "default": {
        "BACKEND": "asgi_ipc.IPCChannelLayer",
        "ROUTING": "my_project.routing.channel_routing",
        "CONFIG": {
            "prefix": "mysite",

Make sure the same settings file is used across all your workers and interface servers; without it, they won’t be able to talk to each other and things will just fail to work.

If you prefer to use RabbitMQ layer, please refer to its documentation. Usually your config will end up like this:

    "default": {
        "BACKEND": "asgi_rabbitmq.RabbitmqChannelLayer",
        "ROUTING": "my_project.routing.channel_routing",
        "CONFIG": {
            "url": "amqp://guest:guest@rabbitmq:5672/%2F",

Run worker servers

Because the work of running consumers is decoupled from the work of talking to HTTP, WebSocket and other client connections, you need to run a cluster of “worker servers” to do all the processing.

Each server is single-threaded, so it’s recommended you run around one or two per core on each machine; it’s safe to run as many concurrent workers on the same machine as you like, as they don’t open any ports (all they do is talk to the channel backend).

To run a worker server, just run:

python runworker

Make sure you run this inside an init system or a program like supervisord that can take care of restarting the process when it exits; the worker server has no retry-on-exit logic, though it will absorb tracebacks from inside consumers and forward them to stderr.

Make sure you keep an eye on how busy your workers are; if they get overloaded, requests will take longer and longer to return as the messages queue up (until the expiry or capacity limit is reached, at which point HTTP connections will start dropping).

In a more complex project, you won’t want all your channels being served by the same workers, especially if you have long-running tasks (if you serve them from the same workers as HTTP requests, there’s a chance long-running tasks could block up all the workers and delay responding to HTTP requests).

To manage this, it’s possible to tell workers to either limit themselves to just certain channel names or ignore specific channels using the --only-channels and --exclude-channels options. Here’s an example of configuring a worker to only serve HTTP and WebSocket requests:

python runworker --only-channels=http.* --only-channels=websocket.*

Or telling a worker to ignore all messages on the “thumbnail” channel:

python runworker --exclude-channels=thumbnail

Run interface servers

The final piece of the puzzle is the “interface servers”, the processes that do the work of taking incoming requests and loading them into the channels system.

If you want to support WebSockets, long-poll HTTP requests and other Channels features, you’ll need to run a native ASGI interface server, as the WSGI specification has no support for running these kinds of requests concurrently. We ship with an interface server that we recommend you use called Daphne; it supports WebSockets, long-poll HTTP requests, HTTP/2 and performs quite well.

You can just keep running your Django code as a WSGI app if you like, behind something like uwsgi or gunicorn; this won’t let you support WebSockets, though, so you’ll need to run a separate interface server to terminate those connections and configure routing in front of your interface and WSGI servers to route requests appropriately.

If you use Daphne for all traffic, it auto-negotiates between HTTP and WebSocket, so there’s no need to have your WebSockets on a separate domain or path (and they’ll be able to share cookies with your normal view code, which isn’t possible if you separate by domain rather than path).

To run Daphne, it just needs to be supplied with a channel backend, in much the same way a WSGI server needs to be given an application. First, make sure your project has an file that looks like this (it should live next to

import os
from channels.asgi import get_channel_layer

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "my_project.settings")

channel_layer = get_channel_layer()

Then, you can run Daphne and supply the channel layer as the argument:

daphne my_project.asgi:channel_layer

Like runworker, you should place this inside an init system or something like supervisord to ensure it is re-run if it exits unexpectedly.

If you only run Daphne and no workers, all of your page requests will seem to hang forever; that’s because Daphne doesn’t have any worker servers to handle the request and it’s waiting for one to appear (while runserver also uses Daphne, it launches worker threads along with it in the same process). In this scenario, it will eventually time out and give you a 503 error after 2 minutes; you can configure how long it waits with the --http-timeout command line argument.

With some browsers you may see errors regarding Sec-WebSocket-Protocol headers. You can set the allowed ws_protocols to match your client protocol like this:

CHANNELS_WS_PROTOCOLS = ["graphql-ws", ]

In production you may start a daphne server without the runserver command. So you need to pass the ws-protocol directly:

daphne --ws-protocol "graphql-ws" --proxy-headers my_project.asgi:channel_layer

Note: The daphne server binds to by default. If you deploy this not locally, bind to your ip or to any ip:

daphne -b -p 8000 --ws-protocol "graphql-ws" --proxy-headers my_project.asgi:channel_layer

Deploying new versions of code

One of the benefits of decoupling the client connection handling from work processing is that it means you can run new code without dropping client connections; this is especially useful for WebSockets.

Just restart your workers when you have new code (by default, if you send them SIGTERM they’ll cleanly exit and finish running any in-process consumers), and any queued messages or new connections will go to the new workers. As long as the new code is session-compatible, you can even do staged rollouts to make sure workers on new code aren’t experiencing high error rates.

There’s no need to restart the WSGI or WebSocket interface servers unless you’ve upgraded the interface server itself or changed the CHANNEL_LAYER setting; none of your code is used by them, and all middleware and code that can customize requests is run on the consumers.

You can even use different Python versions for the interface servers and the workers; the ASGI protocol that channel layers communicate over is designed to be portable across all Python versions.

Running just ASGI

If you are just running Daphne to serve all traffic, then the configuration above is enough where you can just expose it to the Internet and it’ll serve whatever kind of request comes in; for a small site, just the one Daphne instance and four or five workers is likely enough.

However, larger sites will need to deploy things at a slightly larger scale, and how you scale things up is different from WSGI; see Scaling Up.

Running ASGI alongside WSGI

ASGI and its canonical interface server Daphne are both relatively new, and so you may not wish to run all your traffic through it yet (or you may be using specialized features of your existing WSGI server).

If that’s the case, that’s fine; you can run Daphne and a WSGI server alongside each other, and only have Daphne serve the requests you need it to (usually WebSocket and long-poll HTTP requests, as these do not fit into the WSGI model).

To do this, just set up your Daphne to serve as we discussed above, and then configure your load-balancer or front HTTP server process to dispatch requests to the correct server - based on either path, domain, or if you can, the Upgrade header.

Dispatching based on path or domain means you’ll need to design your WebSocket URLs carefully so you can always tell how to route them at the load-balancer level; the ideal thing is to be able to look for the Upgrade: WebSocket header and distinguish connections by this, but not all software supports this and it doesn’t help route long-poll HTTP connections at all.

You could also invert this model, and have all connections go to Daphne by default and selectively route some back to the WSGI server, if you have particular URLs or domains you want to use that server on.

Running on a PaaS

To run Django with channels enabled on a Platform-as-a-Service (PaaS), you will need to ensure that your PaaS allows you to run multiple processes at different scaling levels; one group will be running Daphne, as a pure Python application (not a WSGI application), and the other should be running runworker.

The PaaS will also either have to provide either its own Redis service or a third process type that lets you run Redis yourself to use the cross-network channel backend; both interface and worker processes need to be able to see Redis, but not each other.

If you are only allowed one running process type, it’s possible you could combine both interface server and worker into one process using threading and the in-memory backend; however, this is not recommended for production use as you cannot scale up past a single node without groups failing to work.

Scaling Up

Scaling up a deployment containing channels (and thus running ASGI) is a little different to scaling a WSGI deployment.

The fundamental difference is that the group mechanic requires all servers serving the same site to be able to see each other; if you separate the site up and run it in a few, large clusters, messages to groups will only deliver to WebSockets connected to the same cluster. For some site designs this will be fine, and if you think you can live with this and design around it (which means never designing anything around global notifications or events), this may be a good way to go.

For most projects, you’ll need to run a single channel layer at scale in order to achieve proper group delivery. Different backends will scale up differently, but the Redis backend can use multiple Redis servers and spread the load across them using sharding based on consistent hashing.

The key to a channel layer knowing how to scale a channel’s delivery is if it contains the ! character or not, which signifies a single-reader channel. Single-reader channels are only ever connected to by a single process, and so in the Redis case are stored on a single, predictable shard. Other channels are assumed to have many workers trying to read them, and so messages for these can be evenly divided across all shards.

Django channels are still relatively new, and so it’s likely that we don’t yet know the full story about how to scale things up; we run large load tests to try and refine and improve large-project scaling, but it’s no substitute for actual traffic. If you’re running channels at scale, you’re encouraged to send feedback to the Django team and work with us to hone the design and performance of the channel layer backends, or you’re free to make your own; the ASGI specification is comprehensive and comes with a conformance test suite, which should aid in any modification of existing backends or development of new ones.