Production mode with nginx, django, bokeh

Hi everyone,

I am developing a website using Django, and I have successfully embedded bokeh to visualise data. I am now trying to move the website to production, using nginx, gunicorn and daphne to run the wsgi and asgi application. The website works, however the web-sockets are not reachable.

Here the nginx configuration file:

upstream M2i2 {
  server unix:/run/gunicorn/gunicorn.sock fail_timeout=0;
}

upstream channels-backend {
        server Ip_addres:port;
}


server {

    listen   443 ssl;


    server_name domain_name;

    location  /favicon.ico { access_log off;
                     log_not_found off;
                      }
ssl_certificate ####.cer;
ssl_certificate_key ####.key;
    location /static/ {
                root path_to_static_folder;

        }
        location / {


                proxy_pass http://M2i2;


        proxy_headers_hash_max_size 512;
        proxy_headers_hash_bucket_size 128;
  proxy_connect_timeout 3600;
  proxy_send_timeout 3600;
  proxy_read_timeout 3600;
 

 
  proxy_buffering off;

  client_body_temp_path path_to_temporary_folder;

        }
  client_max_body_size 200G;
client_body_timeout 3600;
    location /ws/ {
       proxy_pass http://channels-backend;
       proxy_send_timeout 3600;
      proxy_read_timeout 3600;

        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        proxy_buffering off;
        proxy_set_header Host $http_host;
        #proxy_redirect off;
        proxy_set_header X-Urls-Scheme $scheme;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Host  $server_name;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header SSL_PROTOCOL $ssl_protocol;

    }
location /media/ {
root path_to_media/;
}

}

I am using a unix:socket to run gunicorn, but I have read that I cannot using that for the websocket so daphne run on a port.

I have added this line

<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">

in my html to avoid Mixed contents

I have setup the routing as follow:

from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from django.apps import apps
from .settings import bokeh_app_module
bokeh_app_config = apps.get_app_config(bokeh_app_module)
#django_asgi_app = get_asgi_application()
application = ProtocolTypeRouter({
    'websocket': AuthMiddlewareStack(URLRouter(bokeh_app_config.routes.get_websocket_urlpatterns())),
    'http': AuthMiddlewareStack(URLRouter(bokeh_app_config.routes.get_http_urlpatterns())),
})

and add the bokeh app in urls.py file

bokeh_apps = [
   
    autoload('history/', history_app.app), 
]

I am using server_document() function to return the URL of Bokeh server application. for example:

def dashboard(request:HttpRequest)-> HttpResponse:
    user = request.user
    userID=user.id
    absolute=request.build_absolute_uri('/')+'history/'
    script2 = server_document(absolute,arguments={"userID":str(user.id) })
    return render(request, 'home/dashboard.html',{'script':script2})

and the script2 result

      <script id="1002">
  (function() {
    const xhr = new XMLHttpRequest()
    xhr.responseType = 'blob';
    xhr.open('GET', "http://domain_name/history/autoload.js?bokeh-autoload-element=1002&bokeh-app-path=/history&bokeh-absolute-url=http://domain_name/history&userID=1", true);
    xhr.onload = function (event) {
      const script = document.createElement('script');
      const src = URL.createObjectURL(event.target.response);
      script.src = src;
      document.body.appendChild(script);
    };
    xhr.send();
  })();
</script>

The page upload but not the websocket and I get this error

  ` GET https://domain_name/history/autoload.js?bokeh-autoload-element=1002&bokeh-app-path=/history&bokeh-absolute-url=http://domain_name/history&userID=1 404 (Not Found)`

I have try to use a different urls in server_document() as follow

absolute=http://Ip_addres:port/+'history/'
    script2 = server_document(absolute,arguments={"userID":str(user.id) })

then the error is

GET https://Ip_address:port/history/autoload.js?bokeh-autoload-element=1002&bokeh-app-path=/history&bokeh-absolute-url=http://Ip_address:port/history&userID=1 net::ERR_CONNECTION_TIMED_OUT

I am bit stuck now, I am not sure what to do

Lucia

I am afraid I can only be of limited help here, I have not used Django in maybe 15 years and my Nginx expertise also minimal. I guess my first question is: why do you think this is a websocket problem? This error:

GET https://domain_name/history/autoload.js?bokeh-autoload-element=1002&bokeh-app-path=/history&bokeh-absolute-url=http://domain_name/history&userID=1 404 (Not Found)

is a standard HTTP 404 error and must be happening before the websocket upgrade is even attempted (autoload.js is what opens the websocket and it is what couldn’t get loaded). The proximate issue actually seems to be that autoload.js is not being served at https://domain_name/history/. (Maybe it should be, or maybe the URL you construct for the app should be different, I couldn’t say.)

Hi Bryan,

Thank you for your reply, I think you are right when the urls path are not with the domain name that why I am overwriting the path with the Ip address and the port and in this case the error is different :
net::ERR_CONNECTION_TIMED_OUT

I am wondering if I need to setup the nginx similar to [this]

# redirect HTTP traffic to HTTPS (optional)
server {
    listen      80;
    server_name foo.com;
    return      301 https://$server_name$request_uri;
}

server {
    listen      443 default_server;
    server_name foo.com;

    # adds Strict-Transport-Security to prevent man-in-the-middle attacks
    add_header Strict-Transport-Security "max-age=31536000";

    ssl on;

    # SSL installation details vary by platform
    ssl_certificate /etc/ssl/certs/my-ssl-bundle.crt;
    ssl_certificate_key /etc/ssl/private/my_ssl.key;

    # enables all versions of TLS, but not the deprecated SSLv2 or v3
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

    # disables all weak ciphers
    ssl_ciphers "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4";

    ssl_prefer_server_ciphers on;

    location / {
        proxy_pass http://127.0.0.1:5100;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_http_version 1.1;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $host:$server_port;
        proxy_buffering off;
    }

}

although this is for a standalone bokeh app that need to be run with

bokeh serve myapp.py --port 5100 --use-xheaders

I am not sure how to add the flag --use-xheaders, which seems to the trick, when bokeh is embedded in django

Thank you

Lucia

I don’t know anything about the bokeh-django integration, if that is what you are using. If so you might want to make an issue there to ask the maintainers directly. Normally use_xheaders is a keyword argument to Server if you are using the server programmatically: bokeh.server.server — Bokeh 3.3.2 Documentation