Websocket Access denied with Apache, Daphne ws/wss

I have a web site I’ve built and runs fine locally using the test server or Daphne but I’ve run into trouble deploying it in the production environment which runs Apache to which I have proxied Daphne to.

Accessing the site from a web browser (so using https) the site displays and works but the web sockets fail. Looking at the terminal output from Daphne I can see the websocket request gets passed from Apache to Daphne but Daphne rejects the request.

Daphne run command

daphne -v 3 -p 8001 -b 127.0.0.1 SODT.asgi:application

Daphne console output

2025-01-13 21:24:46,461 INFO     Starting server at tcp:port=8001:interface=127.0.0.1
2025-01-13 21:24:46,462 INFO     HTTP/2 support not enabled (install the http2 and tls Twisted extras)
2025-01-13 21:24:46,462 INFO     Configuring endpoint tcp:port=8001:interface=127.0.0.1
2025-01-13 21:24:46,463 INFO     HTTPFactory starting on 8001
2025-01-13 21:24:46,463 INFO     Starting factory <daphne.http_protocol.HTTPFactory object at 0x04FB9CC0>
2025-01-13 21:24:46,463 INFO     Listening on TCP address 127.0.0.1:8001
127.0.0.1:30377 - - [13/Jan/2025:21:28:00] "WSCONNECTING /ws/pricefileuploader/uploader/" - -
2025-01-13 21:28:00,889 DEBUG    Upgraded connection ['127.0.0.1', 30377] to WebSocket
2025-01-13 21:28:00,904 INFO     failing WebSocket opening handshake ('Access denied')
2025-01-13 21:28:00,904 WARNING  dropping connection to peer tcp4:127.0.0.1:30377 with abort=False: Access denied
2025-01-13 21:28:00,905 DEBUG    WebSocket ['127.0.0.1', 30377] rejected by application
127.0.0.1:30377 - - [13/Jan/2025:21:28:00] "WSREJECT /ws/pricefileuploader/uploader/" - -
2025-01-13 21:28:00,907 DEBUG    WebSocket closed for ['127.0.0.1', 30377]
127.0.0.1:30377 - - [13/Jan/2025:21:28:00] "WSDISCONNECT /ws/pricefileuploader/uploader/" - -

I’ve looked over a number of topics here and elsewhere online over the past couple of days but have had no luck solving this though I feel I must be close.

My setup is a follows

asgi.py

import os

from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.security.websocket import AllowedHostsOriginValidator
from django.core.asgi import get_asgi_application

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'SODT.settings')
# Initialize Django ASGI application early to ensure the AppRegistry
# is populated before importing code that may import ORM models.
django_asgi_app = get_asgi_application()

import PriceFileUploader.routing
import SupplierUpdater.routing
import FileGenerator.routing

application = ProtocolTypeRouter(
    {
        "http": django_asgi_app,
        "websocket": AllowedHostsOriginValidator(
            AuthMiddlewareStack(
                URLRouter([
                    *PriceFileUploader.routing.websocket_urlpatterns,
                    *SupplierUpdater.routing.websocket_urlpatterns,
                    *FileGenerator.routing.websocket_urlpatterns,
                ])
            )
        ),
    }
)

Apache httpd.conf to proxy to Daphne

RewriteEngine on
RewriteCond %{HTTP:Upgrade} websocket [NC]
RewriteCond %{HTTP:Connection} upgrade [NC]
RewriteRule ^/?(.*) "wss://127.0.0.1:8001/$1" [P,L]
ProxyPass "/SODT/" "http://127.0.0.1:8001/"
ProxyPassReverse "/SODT/" "http://127.0.0.1:8001/"
ProxyPass /ws/ ws://127.0.0.1:8001/ws/
ProxyPassReverse /ws/ ws://127.0.0.1:8001/ws/

routing.py

from django.urls import path
from . import consumers

websocket_urlpatterns = [
    path(r"ws/pricefileuploader/uploader/", consumers.PricefileUploaderConsumer.as_asgi(), name="uploader"),
]

JS entered in web browser debug console to test the connection

var myuploadSocket = new WebSocket('wss://' + window.location.host + '/ws/pricefileuploader/uploader/');

I can’t figure out why the connection is being rejected. Can anyone spot what I’ve missed or have misconfigured? Thanks in advance for any help or insight you might have.

Perhaps this can help: Django websocket issues with Daphne and Apache - #7 by KenWhitesell

Thanks anefta, I had seen that post along with https://forum.djangoproject.com/t/secure-websocket-with-django/37914 and several others but I don’t think that’s the problem. The connection is making it to Apache via wss but the proxy to Daphne should be ws from what I understand and the connection is hitting Daphne. You could be right, I did consider this but I don’t have access to the security cert that Apache is connect with as it’s handled by a third party.

The last post in the first thread linked to here posted a working configuration segment:

    ProxyPass /wss/ wss://127.0.0.1:8001/
    ProxyPassReverse /wss/ wss://127.0.0.1:8001/

Note that in this person’s configuration, they do not specify the path within the proxy target - wss://127.0.0.1:8001/ instead of wss://127.0.0.1:8001/ws/. I would suggest trying by removing the ws/ from the end of the proxy target configuration.

Thanks KenWhitesell. If I change

ProxyPass /ws/ ws://127.0.0.1:8001/ws/
ProxyPassReverse /ws/ ws://127.0.0.1:8001/ws/

to

ProxyPass /ws/ ws://127.0.0.1:8001/
ProxyPassReverse /ws/ ws://127.0.0.1:8001/

Then run the JS console command as above the Daphne out to the console returns

127.0.0.1:56675 - - [14/Jan/2025:12:39:49] "WSCONNECTING /pricefileuploader/uploader/" - -
2025-01-14 12:39:49,610 DEBUG    Upgraded connection ['127.0.0.1', 56675] to WebSocket
2025-01-14 12:39:49,619 INFO     failing WebSocket opening handshake ('Access denied')
2025-01-14 12:39:49,620 WARNING  dropping connection to peer tcp4:127.0.0.1:56675 with abort=False: Access denied
2025-01-14 12:39:49,620 DEBUG    WebSocket ['127.0.0.1', 56675] rejected by application
127.0.0.1:56675 - - [14/Jan/2025:12:39:49] "WSREJECT /pricefileuploader/uploader/" - -
2025-01-14 12:39:49,623 DEBUG    WebSocket closed for ['127.0.0.1', 56675]
127.0.0.1:56675 - - [14/Jan/2025:12:39:49] "WSDISCONNECT /pricefileuploader/uploader/" - -

So now Daphne is trying to match the route as /pricefileuploader/uploader but the routing.py file has it as “ws/pricefileuploader/uploader/”

I also just tried changing the routing.py from

path(r"ws/pricefileuploader/uploader/", consumers.PricefileUploaderConsumer.as_asgi(), name="uploader"),

to

path(r"/ws/pricefileuploader/uploader/", consumers.PricefileUploaderConsumer.as_asgi(), name="uploader"),

to try and match the / on the front of the Daphne output but it made no difference. (restarted Daphne to make sure the changes were run)

I can’t tell if this is failing as it’s not matching the routing string or if it’s being denied for some security reason and I can’t think of any way to tell which case it is. If I put any other path in the JS command Daphne gives the same error showing Access denied for that path which makes me think maybe it is a path matching problem but if that is the case then what is the path it’s trying to match on?

Ok, so removing the ws from the proxy definition was wrong.

Please post the complete server definition section for Apache. It’s possible that there’s something else wrong in it.

You might also want to check the complete request being issued for the websocket in the browser’s developer tools to see what headers are being supplied. Also, for testing purposes, you might want to remove the AllowedHostsOriginValidator from the websocket router definition depending upon what you find regarding the Host header. (What is your ALLOWED_HOSTS setting? What is your DEBUG setting?)

Finally, it might also be worth verifying what exactly Apache is proxying forward to Daphne - you may need to use the --proxy-headers-host parameter with Daphne to identify the Host header to use if you want to use the AllowedHostsOriginValidator.

KenWhitesell. Thank you very much for pointing me in the right direction.

I added --proxy-headers --proxy-headers-host X-Real-IP to my daphne command line but the output didn’t look any different.

I tried removing AllowedHostsOriginValidator (which I’d tried a few days ago but messed it up) and the connection worked.

I had 127.0.0.1 and the local server network IP address in ALLOWED_HOSTS so I added other address’ that were in the Apache access log but that didn’t help.

I changed ALLOWED_HOSTS to [‘*’] and it worked.

Looking in the web browser debug console for the sent header I saw that the value in the header for HOST was the web site address, so I added that to the ALLOWED_HOSTS and it worked.