Django websocket issues with Daphne and Apache

The chat feature works fine on Development, however, when deployed to production, everything on the web app works but the chat. I get the following error

WebSocket connection to 'wss://app.myapp.com/ws/chat/c580d91acbcc4f4fb699e1b6db69249f/' failed: Error during WebSocket handshake: Unexpected response code: 500

I’m using Daphne with Apache Server

This is my Asgi file content

import os
import django
from django.core.asgi import get_asgi_application

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
django.setup()
application = get_asgi_application()

This is my Daphne.service file content


[Unit]
Description=daphne daemon for myapp chat
After=network.target

[Service]
Type=simple
User=root
WorkingDirectory=/home/myusername/myapp/
ExecStart=/home/myusername/virtualenv/myapp/bin/python3.9 /home/myusername/virtualenv/myapp/bin/daphne -b 0.0.0.0 -p 8001 myproject.asgi:application
Restart=on-failure
[Install]
WantedBy=multi-user.target

Daphne is running

Dec 12 15:46:44 server1.mydomain.com python3.9[744788]: 2023-12-12 16:46:44,459 INFO     Listening on TCP address 0.0.0.0:8001
[root@server1 ~]# systemctl status daphne.service
● daphne.service - daphne daemon for myapp chat
   Loaded: loaded (/etc/systemd/system/daphne.service; disabled; vendor preset: disabled)
   Active: active (running) since Tue 2023-12-12 15:46:43 UTC; 6s ago
 Main PID: 744788 (python3.9)
    Tasks: 1 (limit: 12317)
   Memory: 45.7M
   CGroup: /system.slice/daphne.service
           └─744788 /home/myusername/virtualenv/myapp/bin/python3.9 /home/myusername/virtualenv/myapp/bin/daphne -b 0.0.0.0 -p 8001 myproject.asgi:application

Dec 12 15:46:43 server1.mydomain.com systemd[1]: daphne.service: Succeeded.
Dec 12 15:46:43 server1.mydomain.com systemd[1]: Stopped daphne daemon for myapp chat.
Dec 12 15:46:43 server1.mydomain.com systemd[1]: Started daphne daemon for myapp chat.
Dec 12 15:46:44 server1.mydomain.com python3.9[744788]: 2023-12-12 16:46:44,458 INFO     Starting server at tcp:port=8001:interface=0.0.0.0
Dec 12 15:46:44 server1.mydomain.com python3.9[744788]: 2023-12-12 16:46:44,458 INFO     HTTP/2 support enabled
Dec 12 15:46:44 server1.mydomain.com python3.9[744788]: 2023-12-12 16:46:44,459 INFO     Configuring endpoint tcp:port=8001:interface=0.0.0.0
Dec 12 15:46:44 server1.mydomain.com python3.9[744788]: 2023-12-12 16:46:44,459 INFO     Listening on TCP address 0.0.0.0:8001

Same with Redis.

This is my apache config

<VirtualHost 10.10.11.11:80>
    ServerName app.myapp.com
    ServerAlias www.app.myapp.com mail.app.myapp.com

    Redirect permanent / https://app.myapp.com/
</VirtualHost>

LoadModule wsgi_module /home/myusername/virtualenv/myapp/lib64/python3.9/site-packages/mod_wsgi/server/mod_wsgi-py39.cpython-39-x86_64-linux-gnu.so
LoadModule proxy_wstunnel_module modules/mod_proxy_wstunnel.so

<VirtualHost 10.10.11.11:443>
    ServerName app.myapp.com
    ServerAlias www.app.myapp.com mail.app.myapp.com
    DocumentRoot /home/myusername/myapp

    ErrorLog /home/myusername/myapp/error.log
    CustomLog /home/myusername/myapp/access.log combine

    <Directory /home/myusername/myapp/myproject>
        <Files wsgi.py>
            Require all granted
        </Files>
    </Directory>

    Alias /static /home/myusername/app.myapp.com/myapp/static
    <Directory /home/myusername/app.myapp.com/myapp/static>
        Require all granted
    </Directory>

    WSGIDaemonProcess myapp python-home=/home/myusername/virtualenv/myapp python-path=/home/myusername/myapp:/home/myusername/virtualenv/myapp/lib64/python3.9/site-packages
    WSGIProcessGroup myapp
    WSGIScriptAlias / /home/myusername/myapp/myproject/wsgi.py

    WSGIApplicationGroup %{GLOBAL}
    
    # WebSocket Proxy
    #ProxyPass /ws/ ws://app.myapp.com:8001/
    #ProxyPassReverse /ws/ ws://app.myapp.com:8001/
    #ProxyPass / http://0.0.0.0:8001/
    #ProxyPassReverse / http://0.0.0.0:8001/
 
    #ProxyPreserveHost On
    #ProxyRequests Off
    #ProxyPassMatch ^/(ws(/.*)?)$ ws://0.0.0.0:8001/$1
    ProxyPass /ws/ ws://127.0.0.1:8001/
    ProxyPassReverse /ws/ ws://127.0.0.1:8001/
    ProxyPreserveHost On
    ProxyVia On
    
    RewriteEngine on
    RewriteCond %{HTTP:UPGRADE} ^WebSocket$ [NC,OR]
    RewriteCond %{HTTP:CONNECTION} ^Upgrade$ [NC]
    RewriteRule .* ws://0.0.0.0:8001%{REQUEST_URI} [P,QSA,L]
    SSLEngine on
    SSLCertificateFile /var/webuzo/users/myusername/ssl/app.myapp.com-combined.pem

</VirtualHost>

Javascript code

<script>
const chatMessages = document.querySelector('#chat-messages');
const scrollToBottomBtn = document.getElementById('scrollToBottomBtn');
const roomName = JSON.parse(document.getElementById('room-name').textContent);
// Correctly decide between ws:// and wss://
var ws_scheme = window.location.protocol == "https:" ? "wss" : "ws";


{% if debug_mode %}
    var ws_path = ws_scheme + '://' + window.location.host + '/ws/chat/' + roomName + "/"; // development
{% else %}
    var ws_path = ws_scheme + '://' + window.location.host + '/ws/chat/' + roomName + "/"; // production

{% endif %}

const chatSocket = new WebSocket(ws_path);

// the rest of the working code here
</script>

P.S: I’m using Namecheap VPS hosting.

I’d appreciate if anyone can help me figure what the problem is.

Apparently, proxying websockets from Apache is somewhat version-dependent. What version of Apache are you running?

Also, it can be quite confusing with Apache to have multiple potentially-conflicting parameters defined. I would get rid of all the excess and start with the minimum needed to try to establish a connection.

In this case, I’d remove everything associated with the Proxy except for the one line identified in the docs at mod_proxy - Apache HTTP Server Version 2.5

ProxyPass "/some/ws/capable/path/" "http://example.com/some/ws/capable/path/" upgrade=websocket

which would translate to something like:
ProxyPass "/ws/" "ws://127.0.0.1:8001/ws/" upgrade=websocket
(You don’t show your Protocol router or the urls being used, so this is a bit of a guess.)

Or, based upon those docs, it might even be something like:
ProxyPass "/ws/" "http://127.0.0.1:8001/ws/" upgrade=websocket

If this doesn’t work, then it would be helpful to see both the Apache error logs along with the Daphne logs.

(Side note: I haven’t had to configure an Apache server for more than 10 years now, and have never used Apache with websockets, so this is a learning experience for both of us.)

My apache version is 2.4.57. II’ve checked the Apache and Daphne logs, and there are no errors. I’ve also tried the approach you suggested and that still didn’t work. What server do you use for websockets? Nginx?

What works for you?

I now use Nginx for every webserver deployment (including websockets). My last Apache installation would have been about 2010/2011, and my last Apache http server was retired around 2016 (I think).

If you want assistance with continuing to diagnose this, we’ll need more details than just saying “it didn’t work”. It would start with posting the Apache error log, and checking the browser console log. It might also be worth having the browser’s network tab open when you try to open the connection to see what it reports.

May I know why you switched from Apache to Nginx?

Ok, so it looks like it’s trying to proxy wss through to Daphne instead of handling the ssl endpoint itself. You could try running Daphne using the same cert as with Apache, allowing it (Daphne) to be an SSL endpoint. See the docs at GitHub - django/daphne: Django Channels HTTP/WebSocket server for information as to how to set that up.

Too many problems encountered with mod_wsgi, too difficult to configure for what we wanted to do, significantly better performance with static file retrieval. (Again, this was back about 10 years ago. What was true then may not be true any more. But we’ve been extremely happy with Nginx and haven’t encountered any reason to even think about going back.)

Thanks a lot for your help. I had to use Nginx instead and it worked seamlessly. But guess what?! I got it working with Apache just now haha.

This was what I used in config for the websocket and it worked.

    RewriteEngine on
    RewriteCond %{HTTP:UPGRADE} ^WebSocket$ [NC,OR]
    RewriteCond %{HTTP:CONNECTION} ^Upgrade$ [NC]
    RewriteRule .* ws://127.0.0.1:8001%{REQUEST_URI} [P,QSA,L]
    ProxyPass /wss/ wss://127.0.0.1:8001/
    ProxyPassReverse /wss/ wss://127.0.0.1:8001/

Now I don’t know if I’m to continue with Apache or switch to Nginx lol.