notebook icon indicating copy to clipboard operation
notebook copied to clipboard

Nginx + notebook issue

Open jsalva opened this issue 9 years ago • 27 comments

Hi guys,

I've been trying to get my jupyter notebook to run behind an nginx reverse proxy and everything seems to be going well, until i actually try to connect to the kernel in a notebook. Here's my /etc/nginx/conf.d/configuration.conf configuration upfront:

upstream notebook {
    server localhost:8888;
}

server {
    listen                    443;
    ssl                       on;
    server_name               notebook.mydomain.com www.notebook.mydomain.com;
    ssl_certificate           /home/<user>/certificates/ipythoncert.pem;
    ssl_certificate_key       /home/<user>/certificates/ipythoncert.pem;
    ssl_protocols             TLSv1 TLSv1.1 TLSv1.2;
    access_log                /home/<user>/.jupyter/log/nginx.access.log;
    error_log                 /home/<user>/.jupyter/log/nginx.error.log;

    location / {
        proxy_pass            https://notebook;
        proxy_set_header      Host $host;
    }

    location /api/kernels/ {
        proxy_pass            https://notebook;
        proxy_set_header      Host $host;
        # websocket support
        proxy_http_version    1.1;
        proxy_set_header      Upgrade "websocket";
        proxy_set_header      Connection "Upgrade";
        proxy_read_timeout    86400;
    }
}

Also, here's my ~/.jupyter/jupyter_notebook_config.py:

c.NotebookApp.ip = 'localhost'
c.NotebookApp.allow_origin = '*'
c.NotebookApp.certfile = u'/home/<user>/certificates/ipythoncert.pem'
c.NotebookApp.open_browser = False
c.NotebookApp.password = u'sha1:imnotthatstupiddontworry'
c.NotebookApp.trust_xheaders = True
#everything else is left commented

On the client, the <div id="notification_kernel">...</div> won't stop displaying "connecting to kernel", and I can't get my cells to run.

As it retries, I get a popup with "A connection to the notebook server could not be established. The notebook will continue trying to reconnect, but until it does, you will NOT be able to run code. Check your network connection or notebook server configuration."

and, the javascript console:

> Default extension for cell metadata editing loaded.
> Raw Cell Format toolbar preset loaded.
> Slideshow extension for metadata editing loaded.
> https://notebook.mydomain.com/nbextensions/widgets/notebook/js/extension.js Failed to load resource: the server responded with a status of 404 (Not Found)
> ipywidgets package not installed.  Widgets are not available.
> Session: kernel_created (5e294362-a965-46c0-9bef-cfd052f91f9e)
> Starting WebSockets: wss://notebook.mydomain.com/api/kernels/a7fdec59-190e-405b-bd2f-35126d0b8981
> WebSocket connection to 'wss://notebook.mydomain.com/api/kernels/a7fdec59-190e-405b-bd2f-35126d0b8981/channels?session_id=<session_id>' failed: Error during  WebSocket handshake: Unexpected response code: 504
> Kernel: kernel_disconnected (a7fdec59-190e-405b-bd2f-35126d0b8981)
> WebSocket connection failed:  wss://notebook.mydomain.com/api/kernels/<kernel_id>
> Connection lost, reconnecting in 1 seconds.
> Kernel: kernel_reconnecting (a7fdec59-190e-405b-bd2f-35126d0b8981)
...
more failures
...

(Edit: I just noticed that in the above javascript console output, the id following the kernel_created message is different from all the other ids used in e.g. the kernel URLs; my guess is that isn't relevant at all, but I'm running out of ideas...)

from the server's perspective (loglevel "DEBUG"):

[D 21:48:20.451 NotebookApp] Using contents: services/contents
[D 21:48:20.452 NotebookApp] 200 GET /notebooks/Odd%20jobs.ipynb (<my-IP-address>) 1.86ms
[D 21:48:20.574 NotebookApp] 200 GET /static/components/jquery-ui/themes/smoothness/jquery-ui.min.css?v=9b2c8d3489227115310662a343fce11c (<my-IP-address>) 0.82ms
[D 21:48:20.583 NotebookApp] 200 GET /static/components/MathJax/MathJax.js?config=TeX-AMS_HTML-full,Safe&delayStartupUntil=configured (<my-IP-address>) 0.77ms
[D 21:48:20.588 NotebookApp] 200 GET /static/components/bootstrap-tour/build/css/bootstrap-tour.min.css?v=d0b3c2fce6056a2ddd5a4513762a94c4 (<my-IP-address>) 0.69ms
[D 21:48:20.594 NotebookApp] 200 GET /static/components/codemirror/lib/codemirror.css?v=549e7432c1e8e94d1a2e66d7be92a430 (<my-IP-address>) 0.63ms
[D 21:48:20.596 NotebookApp] 200 GET /static/style/style.min.css?v=726bda814675157da88304dabb766757 (<my-IP-address>) 1.27ms
[D 21:48:20.610 NotebookApp] 200 GET /static/notebook/css/override.css?v=e6f18013b8771987812e992b38ec3318 (<my-IP-address>) 0.75ms
[D 21:48:20.622 NotebookApp] 200 GET /custom/custom.css (<my-IP-address>) 0.75ms
[D 21:48:20.635 NotebookApp] 200 GET /static/components/es6-promise/promise.min.js?v=f004a16cb856e0ff11781d01ec5ca8fe (<my-IP-address>) 0.55ms
[D 21:48:20.637 NotebookApp] 200 GET /static/components/requirejs/require.js?v=2de44fdcc1fe5e939aa4ce80626b241d (<my-IP-address>) 0.78ms
[D 21:48:20.651 NotebookApp] 200 GET /static/components/text-encoding/lib/encoding.js?v=d5bb0fc9ffeff7d98a69bb83daa51052 (<my-IP-address>) 0.87ms
[D 21:48:20.665 NotebookApp] 200 GET /static/notebook/js/main.min.js?v=40e10638fcf65fc1c057bff31d165e9d (<my-IP-address>) 5.20ms
[D 21:48:20.680 NotebookApp] 200 GET /static/base/images/logo.png?v=7c4597ba713d804995e8f8dad448a397 (<my-IP-address>) 0.70ms
[D 21:48:20.784 NotebookApp] 200 GET /static/components/MathJax/config/TeX-AMS_HTML-full.js?rev=2.5.3 (<my-IP-address>) 1.57ms
[D 21:48:20.914 NotebookApp] 200 GET /static/style/style.min.css.map (<my-IP-address>) 1.13ms
[D 21:48:21.387 NotebookApp] 200 GET /static/components/MathJax/config/Safe.js?rev=2.5.3 (<my-IP-address>) 0.68ms
[D 21:48:21.877 NotebookApp] 200 GET /static/notebook/js/main.min.js.map (<my-IP-address>) 5.87ms
[D 21:48:22.066 NotebookApp] 200 GET /static/services/contents.js?v=20151020214716 (<my-IP-address>) 1.13ms
[D 21:48:22.080 NotebookApp] 200 GET /custom/custom.js?v=20151020214716 (<my-IP-address>) 0.74ms
[D 21:48:22.132 NotebookApp] 200 GET /api/config/notebook?_=1445377700749 (<my-IP-address>) 0.74ms
[D 21:48:22.135 NotebookApp] 200 GET /api/config/common?_=1445377700750 (<my-IP-address>) 0.54ms
[D 21:48:22.491 NotebookApp] Native kernel (python2) available from /home/<user>/.virtualenvs/<env>/lib/python2.7/site-packages/ipykernel/resources
[D 21:48:22.491 NotebookApp] Native kernel (python2) available from /home/<user>/.virtualenvs/<env>/lib/python2.7/site-packages/ipykernel/resources
[D 21:48:22.491 NotebookApp] 200 GET /api/kernelspecs (<my-IP-address>) 1.36ms
[D 21:48:22.496 NotebookApp] 200 GET /static/components/font-awesome/fonts/fontawesome-webfont.woff?v=4.2.0 (<my-IP-address>) 0.82ms
[D 21:48:22.541 NotebookApp] 200 GET /api/contents/Odd%20jobs.ipynb?type=notebook&_=1445377700751 (<my-IP-address>) 9.91ms
[D 21:48:22.671 NotebookApp] Using contents: services/contents
[W 21:48:22.672 NotebookApp] 404 GET /nbextensions/widgets/notebook/js/extension.js?v=20151020214716 (<my-IP-address>) 1.60ms referer=https://notebook.mydomain.com/notebooks/Odd%20jobs.ipynb
[D 21:48:22.729 NotebookApp] 200 GET /static/components/MathJax/jax/output/HTML-CSS/fonts/STIX-Web/fontdata.js?rev=2.5.3 (<my-IP-address>) 0.72ms
[D 21:48:23.067 NotebookApp] 201 POST /api/sessions (<my-IP-address>) 0.86ms
[D 21:48:23.070 NotebookApp] 200 GET /api/contents/Odd%20jobs.ipynb/checkpoints?_=1445377700752 (<my-IP-address>) 0.70ms
[D 21:48:23.089 NotebookApp] Native kernel (python2) available from /home/<user>/.virtualenvs/<env>/lib/python2.7/site-packages/ipykernel/resources
[D 21:48:23.089 NotebookApp] Serving kernel resource from: /home/<user>/.virtualenvs/<env>/lib/python2.7/site-packages/ipykernel/resources
[D 21:48:23.090 NotebookApp] 200 GET /kernelspecs/python2/logo-64x64.png (<my-IP-address>) 1.45ms
[D 21:48:23.165 NotebookApp] 200 GET /static/components/MathJax/extensions/Safe.js?rev=2.5.3 (<my-IP-address>) 0.68ms
[D 21:48:23.360 NotebookApp] Initializing websocket connection /api/kernels/5bffc82f-1765-4fc4-b3cc-3a634ac571d9/channels
[D 21:48:23.363 NotebookApp] Opening websocket /api/kernels/5bffc82f-1765-4fc4-b3cc-3a634ac571d9/channels
[D 21:48:23.363 NotebookApp] Connecting to: tcp://127.0.0.1:47444
[D 21:48:23.364 NotebookApp] Connecting to: tcp://127.0.0.1:36129
[D 21:48:23.364 NotebookApp] Connecting to: tcp://127.0.0.1:53437

The only way I got the websockets to connect to the kernel properly was to bypass nginx and set

c.NotebookApp.ip = '*'

Then, when I access it from the https://ec2-url-for-my-instance:8888 (after clicking through the security warnings that pop up with my self-signed certificate), the websockets will connect to the kernel and I can go on my merry way running whatever I want...

I have been trying to determine why the nginx proxy isn't working for almost 2 days now.

Does anyone have any ideas about what's going on?

jsalva avatar Oct 20 '15 21:10 jsalva

@rgbkrk ideas? @jsalva have you tried switch from 'localhost' to '127.0.0.1' explicitly?

minrk avatar Oct 21 '15 13:10 minrk

I just tried updating every occurrence of localhost to 127.0.0.1 in ~/.jupyter/jupyter_notebook_config.py, as well as in /etc/nginx/conf.d/configuration.conf, to no avail

jsalva avatar Oct 21 '15 18:10 jsalva

@jsalva did you get anywhere with this? I'm doing something similar with apache and running into the same problem.

ianabc avatar Nov 17 '15 18:11 ianabc

I wish I could have some helpful updates, but nope, gave up and moved on.

jsalva avatar Nov 17 '15 18:11 jsalva

I run mine behind a reverse proxy on Apache, and it took me a full evening of googling and editing my configuration and restarting, but I was able to get it with these directives:

<Location /notebook>
    RequestHeader unset Accept-Encoding
    ProxyPass        http://notebook:8888/notebook
    ProxyPassReverse http://notebook:8888/notebook
    ProxyPreserveHost on
    Order allow,deny
    Allow from all
</Location>
<Location /notebook/api/kernels/>
    ProxyPass        ws://notebook:8888/notebook/api/kernels/
    ProxyPassReverse ws://notebook:8888/notebook/api/kernels/
</Location>

Had to enable some additional modules for Apache for this to work (on Ubuntu 14.04 LTS):

sudo a2enmod proxy
sudo a2enmod proxy_http
sudo a2enmod proxy_https
sudo a2enmod proxy_wstunnel
sudo a2enmod headers

Also, in my .jupyter/jupyter_notebook_config.py I changed the base URL for the notebook (since it's being hosted as a virtual folder this makes the reverse proxying easier):

c.NotebookApp.base_url = '/notebook'

@ianabc @jsalva Feel free to ping me for discussion. I am not 100% sure what your situations are, or if this helps, and I am hardly an Apache guy, but I did manage to make it work.

markoverholser avatar Nov 17 '15 19:11 markoverholser

@hosemn I was missing the proxy_wstunnel module, when turned on debugging I could see the protocol error. Thanks for your help!

ianabc avatar Nov 17 '15 20:11 ianabc

Glad it worked for you, and I hope it helps other Apache users. As for nginx, I am not sure where to start.

markoverholser avatar Nov 17 '15 21:11 markoverholser

Hmm, I also cannot get nginx proxy or a caddyserver proxy working. I can get pretty close, can log in and create and delete files, but cannot connect to a python kernel (or launch a terminal). Must have something to do with the websockets proxy, but it's beyond me. The Jupyter logs just show 400 errors like: [W 01:51:26.202 NotebookApp] 400 GET /api/kernels/aa45474d-7fb8-4685-98dc-5ebeba378f41/channels?session_id=1A9BFB019EE0472486650C81705BCC73 (172.17.0.1) 5.77ms referer=None

My nginx conf is here: https://gist.github.com/cboettig/8643341bd3c93b62b5c2 (simple variations, e.g. serving from root instead of a subdomain, or serving without the ssl parts still give me the same issue). Any suggestions?

cboettig avatar Dec 31 '15 02:12 cboettig

Don't know if it will help, but here is a working nginx config file for jupyterhub:

https://github.com/calpolydatascience/jupyterhub-deploy-data301/blob/master/roles/nginx/templates/nginx.conf.j2

On Wed, Dec 30, 2015 at 6:01 PM, Carl Boettiger [email protected] wrote:

Hmm, I also cannot get nginx proxy or a caddyserver proxy working. I can get pretty close, can log in and create and delete files, but cannot connect to a python kernel (or launch a terminal). Must have something to do with the websockets proxy, but it's beyond me. The Jupyter logs just show 400 errors like: [W 01:51:26.202 NotebookApp] 400 GET /api/kernels/aa45474d-7fb8-4685-98dc-5ebeba378f41/channels?session_id=1A9BFB019EE0472486650C81705BCC73 (172.17.0.1) 5.77ms referer=None

My nginx conf is here: https://gist.github.com/cboettig/8643341bd3c93b62b5c2 (simple variations, e.g. serving from root instead of a subdomain, or serving without the ssl parts still give me the same issue). Any suggestions?

— Reply to this email directly or view it on GitHub https://github.com/jupyter/notebook/issues/625#issuecomment-168110162.

Brian E. Granger Associate Professor of Physics and Data Science Cal Poly State University, San Luis Obispo @ellisonbg on Twitter and GitHub [email protected] and [email protected]

ellisonbg avatar Dec 31 '15 05:12 ellisonbg

@ellisonbg Thanks for the suggestion, I've actually tried that config as well (after putting in the appropriate values for the ansible-template chunks), and I get the exact same issue -- nginx shows 400 errors trying to connect to the kernel, and Jupyter opens but cannot reach a python kernel...

I'm running the jupyter instance via a docker container from jupyter/docker-stacks; but don't see why that should be messing up the connection to the kernel when behind a proxy...

cboettig avatar Dec 31 '15 05:12 cboettig

@cboettig Exact same issue here (using nginx in front of https://hub.docker.com/r/jupyter/jupyterhub/~/dockerfile/ ). No idea what could be wrong.

nginx tells me "client closed connection while waiting for request"

Update: Turns out my issue was that my nginx server was behind an aws ELB (for https). ELBs need special configs for websockets. I ended up just bypassing the ELB.

rcompton avatar Jan 05 '16 20:01 rcompton

@rcompton Thanks, turned out my issue was also websockets related (and had hub-style redirects with user bit in my previous file). Here's the tweaked nginx working for me now: https://gist.github.com/cboettig/8643341bd3c93b62b5c2

cboettig avatar Jan 08 '16 20:01 cboettig

It works for me using jsalva's config with one small change:

location ~ /api/kernels/ {

nicerobot avatar Jun 11 '16 05:06 nicerobot

@nicerobot's suggestion worked for me. Thanks!

AlfioEmanueleFresta avatar Jul 27 '16 12:07 AlfioEmanueleFresta

This worked for me, based on jsalva's post (behind VPN so no SSL):

server {
    listen 80 default_server;

    location / {
        proxy_pass            http://localhost:{{ jupyter_server_port }};
        proxy_set_header      Host $host;
    }

    location /api/kernels/ {
        proxy_pass            http://localhost:{{ jupyter_server_port }};
        proxy_set_header      Host $host;
        # websocket support
        proxy_http_version    1.1;
        proxy_set_header      Upgrade "websocket";
        proxy_set_header      Connection "Upgrade";
        proxy_read_timeout    86400;
    }
}

qqrs avatar Mar 21 '17 21:03 qqrs

Just ran into this issue, and tried the solution by @qqrs, but it isn't exactly future-proof and some functionalities like spawning terminals still didn't work. I dug a little deeper and found that websockets in Nginx only needs a specified http version, and being able to upgrade the connection into a tunnel. I currently have this config working:

map $http_upgrade $connection_upgrade {
        default upgrade;
        ''      close;
}

server {
        listen 80;
        server_name jupyter.somewhere.cool;

        location / {
                proxy_pass            http://localhost:9999;
                proxy_set_header      Host $host;
                proxy_http_version    1.1;
                proxy_set_header      Upgrade $http_upgrade;
                proxy_set_header      Connection $connection_upgrade;
        }
}

See http://nginx.org/en/docs/http/websocket.html

wintermelons avatar Feb 05 '18 05:02 wintermelons

@wintermelons it's awesome, you saved my day!

phariel avatar Mar 13 '18 02:03 phariel

I'm still getting the same error even with the solution by @qqrs above. I'm running the notebook server in gcloud with https and OAuth2. Has anyone managed to get it to work in a similar setup?

csymeonides avatar Mar 23 '18 09:03 csymeonides

Using proxy_set_header Host $host; doesn't work for me. I got "API request failed (403): Forbidden" when I clicked "Stop My Server" button. Error from Jupyterhub log: 403 DELETE /hub/api/users/tester/server (@10.10.102.28)

Changing to proxy_set_header Host $http_host; does solve my issue. So now when I clicked the button, Jupyterhub produce log output with: 204 DELETE /hub/api/users/tester/server ([email protected])

nikAizuddin avatar May 07 '18 12:05 nikAizuddin

I'm running Jupyter behind an ssl reverse proxy. @wintermelons solution did work for me, when accessing the notebook from Firefox - but not from Safari where I cannot connect to the notebook kernel and get a 401 error on the web socket call:

[Error] WebSocket connection to 'wss://somedomain.com/notebooks/api/kernels/6343900b-12f3-4245-81cd-8c1e3642d6b5/channels?session_id=DF7A59E736A041458B79DD76D45AC89B' failed: Unexpected response code: 401

Maybe that does explain the different results people are reporting here.

reichardtj avatar Sep 06 '18 11:09 reichardtj

I have to set proxy_read_timeout to a large value like 86400, otherwise, browser console continues reconnecting. https://github.com/jupyterhub/jupyterhub/issues/3368

372046933 avatar Feb 24 '21 08:02 372046933

I have to set proxy_read_timeout to a large value like 86400, otherwise, browser console continues reconnecting. jupyterhub/jupyterhub#3368

proxy_read_timeout = 60s is enough. Because the ping-pong interval is 30s

372046933 avatar Feb 25 '21 05:02 372046933

For me proxy_set_header Origin ""; solved the problem (SSL on). Reference (in the comments): https://stackoverflow.com/a/23912400

murleo avatar May 15 '21 22:05 murleo

Thanks for your code. I advise you to check also if the configuration file (a python file) has no coding error

jojupiter avatar Mar 30 '23 07:03 jojupiter

Thanks to whoever mentioned web sockets. I am using nginx proxy manager for reverse proxy and all I had to do was tick the "websockets support" to ON for jupyter. This fixed the "400 GET" error logs towards api/kernels in the docker container for jupyer.

LaurentiuAndrei avatar Jun 19 '23 10:06 LaurentiuAndrei

this helped me

ingress:
  enabled: true
  annotations:
    nginx.org/websocket-services: "proxy-public"
  hosts:
    - example.com

I used helm chart

Gakhramanzode avatar Apr 19 '24 17:04 Gakhramanzode