Set up multiple jupyterhub on same domain with different ports using nginx

Hello, I have set up two vms with Jupyterhub and all seems to be working as expected, I have used the same domain jhub.example.com and then jhub.example.com:1234 and I can access the IUs without issues.

I thin my problem is with nginx configuration, as when I try to go to the admin page and stop the server I get 403. Only at this time.

I can restart the service and it works just fine, but 403 and CORS error in the logs when I try it from the UI.

I have tried different configuration blocks inside my sites-enabled/jupyterhub.conf but still no luck.

Can anybody provide a working configuration example?

I am using Azure AD for authentication, it’s running on Ubuntu 20.04, and this runs docker containers, spawning a new instance per new user (this works)

Thank you

Hi! Are you seeing problems with both of your JupyterHubs, or only one? Can you show us your Nginx and JupyterHub configs with secrets redacted? Thanks!

Hi, thank you for your reply. No, I have just confirmed only vm 2 returns 403 when trying to ‘Stop my server’ from the hub control panel. I will share the configurations for vm01 - jhub.example .com and vm02 jhub.example. com:1234. (Sorry but some url have whitespaces as the forum wouldn’t allow me to post more then 2 urls, they are correct in my actual code)

nginx configuration for vm01

server {
        listen 80;
        return 301 https:/ /$host$request_uri;
}
server {
    listen               443 ssl;
    ssl_reject_handshake on;
}

server {
        listen 443 ssl;
        server_name jhub.example. com;

        ssl_certificate         /etc/ssl/jupyterhub/certs/ssl.jupyterhub.cert;
        ssl_certificate_key     /etc/ssl/jupyterhub/private/ssl.jupyterhub.key;

        ssl_session_cache builtin:1000 shared:SSL:10m;
        ssl_protocols TLSv1.2;
        ssl_ciphers HIGH:!aNULL:!eNULL:!EXPORT:!CAMELLIA:!DES:!MD5:!PSK:!RC4;
        ssl_prefer_server_ciphers on;


        access_log      /var/log/nginx/jupyterhub.access.log;

        location / {
                proxy_set_header        Host $host;
                proxy_set_header        X-Real-IP $remote_addr;
                proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header        X-Forwarded-Proto $scheme;

                proxy_pass              https ://127.0.0.1:8000;
                proxy_read_timeout      90;

                proxy_redirect          https ://127.0.0.1:8000 https ://jhub.example;

                # WebSockets support
                proxy_http_version      1.1;
                proxy_set_header        Upgrade $http_upgrade;
                proxy_set_header        Connection "upgrade";
        }
}

nginx configuration for vm02

server {
        listen 80;
        return 301 https:/ /$host$request_uri;
}
server {
    listen               443 ssl;
    ssl_reject_handshake on;
}

server {
	 listen 1234 ssl default_server;
	 server_name jhub.example:1234;
	 ssl_certificate /etc/ssl/jupyterhub/certs/ssl.jupyterhub.cert; 
	 ssl_certificate_key /etc/ssl/jupyterhub/private/ssl.jupyterhub.key;
	 location / {
                proxy_pass "https:/ /127.0.0.1:1234";
		proxy_set_header Host $host;
	}
    }

server {
        listen 443 ssl default_server;
        server_name jhub.example;

        ssl_certificate         /etc/ssl/jupyterhub/certs/ssl.jupyterhub.cert;
        ssl_certificate_key     /etc/ssl/jupyterhub/private/ssl.jupyterhub.key;

        ssl_session_cache builtin:1000 shared:SSL:10m;
        ssl_protocols TLSv1.2;
        ssl_ciphers HIGH:!aNULL:!eNULL:!EXPORT:!CAMELLIA:!DES:!MD5:!PSK:!RC4;
        ssl_prefer_server_ciphers on;


        access_log      /var/log/nginx/jupyterhub.access.log;

        location / {
                proxy_set_header        Host $host;
                proxy_set_header        X-Real-IP $remote_addr;
                proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header        X-Forwarded-Proto $scheme;

                proxy_pass              https:/ /127.0.0.1:8000;
                proxy_read_timeout      90;

                proxy_redirect          https:/ /127.0.0.1:8000 https:/ /jhub.example:1234;

                # WebSockets support
                proxy_http_version      1.1;
                proxy_set_header        Upgrade $http_upgrade;
                proxy_set_header        Connection "upgrade";
       }
}

Jupyterhub_config.py file, almost the same for both vms, only port changes.

from oauthenticator.azuread import LocalAzureAdOAuthenticator
from jupyter_client.localinterfaces import public_ips
from dockerspawner import DockerSpawner
from tornado import gen
from jupyterhub.auth import LocalAuthenticator
import re
import os
import docker
import json
import shlex
import subprocess
import pwd
c.Application.log_level = 'DEBUG'
c.Spawner.args = ['--NotebookApp.allow_origin=*']
c.Spawner.http_timeout = 300
c.Spawner.start_timeout = 300

c.JupyterHub.ssl_key = '/etc/ssl/jupyterhub/private/ssl.jupyterhub.key'
c.JupyterHub.ssl_cert = '/etc/ssl/jupyterhub/certs/ssl.jupyterhub.cert'
c.JupyterHub.port = 443
host_name = 'jhub.example. com' #[this url is jhub.example. com:1234 in the vm02]
tenant_id = ''
client_id = ''
client_secret = ''
default_admin = ''
image_map_file_name = "image_map.json"
azure_storage_name=""
azure_file_share_name=""
start_ssh_port_mapping=5101

c.Spawner.default_url = '/lab'
c.JupyterHub.spawner_class = CustomDockerSpawner
c.JupyterHub.logo_file = "/home/jupyterhub/logo.png"
c.DockerSpawner.extra_host_config = {'runtime': 'nvidia', 'pid_mode': 'host', 'cap_drop': ['SYS_MODULE', 'SYS_RAWIO', 'SYS_PACCT', 'SYS_ADMIN', 'SYS_NICE', 'SYS_RESOURCE', 'SYS_TIME', 'SYS_TTY_CONFIG', 'AUDIT_CONTROL', 'MAC_ADMIN',
                                                                                           'MAC_OVERRIDE', 'NET_ADMIN', 'SYSLOG', 'DAC_READ_SEARCH', 'LINUX_IMMUTABLE', 'NET_BROADCAST', 'IPC_LOCK', 'IPC_OWNER', 'SYS_BOOT', 'LEASE', 'WAKE_ALARM', 'BLOCK_SUSPEND'], 'privileged': True, 'devices': ['/dev/fuse']}
c.DockerSpawner.extra_create_kwargs = {'user': 'root'}
c.DockerSpawner.environment = {
    'GRANT_SUDO': '1', 'NVIDIA_DRIVER_CAPABILITIES': 'compute,utility', 'NVIDIA_VISIBLE_DEVICES': 'all'}
c.DockerSpawner.remove = True

with open(image_map_file_name, "r") as image_map_file:
    image_map = json.load(image_map_file)

c.DockerSpawner.image_whitelist = image_map

c.DockerSpawner.notebook_dir = azfiles_dir

c.DockerSpawner.volumes = {'jupyterhub-user-{user}': azfiles_dir,
                           'jupyterhub-ssdp-{user}': ssdprivate_data_dir, 'jupyterhub-ssd': ssdshared_data_dir}

ip = public_ips()[0]

c.JupyterHub.ip = '0.0.0.0'
c.JupyterHub.port = 8000
c.JupyterHub.hub_ip = ip

c.JupyterHub.authenticator_class = NormalizedUsernameLocalAzureAdOAuthenticator

c.Authenticator.admin_users = {default_admin}

c.LocalAuthenticator.create_system_users = True
c.Authenticator.delete_invalid_users = True
c.LocalAzureAdOAuthenticator.tenant_id = tenant_id
c.LocalAzureAdOAuthenticator.oauth_callback_url = 'https:/ /{}/hub/oauth_callback'.format(
    host_name)
c.LocalAzureAdOAuthenticator.client_id = client_id
c.LocalAzureAdOAuthenticator.client_secret = client_secret

There are parts of the code in the config file that have been removed, this is working for me as it’s been up for some time now without issues. Only problem there is now is the 403 when stopping.

Thank you for your help.

Thanks, could you also dig out your JupyterHub logs around the time of the error? If you can enable debug logging that would be even better!

Hello, thanks again for your reply.

This is the only thing that happens when trying to stop my server, there is a 403 and CORS error, the rest is usual debug logging. See the first two lines below:

Apr 13 12:52:12 VM02 bash[1784]: [W 2021-04-13 12:52:12.152 JupyterHub log:174] 403 DELETE /hub/api/users/someusername/server (@123.12.123.123) 2.35ms
Apr 13 12:52:12 VM02 bash[1784]: [W 2021-04-13 12:52:12.151 JupyterHub base:57] Blocking Cross Origin API request.  Referer: https://jhub.example.com:1234/hub/home, Host: jhub.example.com/hub/
Apr 13 12:52:11 VM02 bash[1784]:      'Status': 'running'}
Apr 13 12:52:11 VM02 bash[1784]:      'StartedAt': '2021-03-30T22:19:11.358150395Z',
Apr 13 12:52:11 VM02 bash[1784]:      'Running': True,
Apr 13 12:52:11 VM02 bash[1784]:      'Restarting': False,
Apr 13 12:52:11 VM02 bash[1784]:      'Pid': 8226,
Apr 13 12:52:11 VM02 bash[1784]:      'Paused': False,
Apr 13 12:52:11 VM02 bash[1784]:      'OOMKilled': False,
Apr 13 12:52:11 VM02 bash[1784]:      'FinishedAt': '0001-01-01T00:00:00Z',
Apr 13 12:52:11 VM02 bash[1784]:      'ExitCode': 0,
Apr 13 12:52:11 VM02 bash[1784]:      'Error': '',
Apr 13 12:52:11 VM02 bash[1784]: [D 2021-04-13 12:52:11.468 JupyterHub dockerspawner:761] Container 8cc9281 status: {'Dead': False,
Apr 13 12:52:11 VM02 bash[1784]: [D 2021-04-13 12:52:11.465 JupyterHub dockerspawner:777] Getting container 'jupyter-someotheruser'
Apr 13 12:52:04 VM02 bash[1784]:      'Status': 'running'}
Apr 13 12:52:04 VM02 bash[1784]:      'StartedAt': '2021-03-30T22:06:34.843226462Z',
Apr 13 12:52:04 VM02 bash[1784]:      'Running': True,
Apr 13 12:52:04 VM02 bash[1784]:      'Restarting': False,
Apr 13 12:52:04 VM02 bash[1784]:      'Pid': 6614,
Apr 13 12:52:04 VM02 bash[1784]:      'Paused': False,
Apr 13 12:52:04 VM02 bash[1784]:      'OOMKilled': False,
Apr 13 12:52:04 VM02 bash[1784]:      'FinishedAt': '0001-01-01T00:00:00Z',
Apr 13 12:52:04 VM02 bash[1784]:      'ExitCode': 0,
Apr 13 12:52:04 VM02 bash[1784]:      'Error': '',
Apr 13 12:52:04 VM02 bash[1784]: [D 2021-04-13 12:52:04.987 JupyterHub dockerspawner:761] Container 2158e20 status: {'Dead': False,
Apr 13 12:52:04 VM02 bash[1784]: [D 2021-04-13 12:52:04.984 JupyterHub dockerspawner:777] Getting container 'jupyter-someusername'
Apr 13 12:52:03 VM02 bash[1784]: [W 2021-04-13 12:52:03.616 JupyterHub log:174] 403 DELETE /hub/api/users/someusername/server (@123.12.123.123) 3.13ms
Apr 13 12:52:03 VM02 bash[1784]: [W 2021-04-13 12:52:03.615 JupyterHub base:57] Blocking Cross Origin API request.  Referer: https://jhub.example.com:1234/hub/home, Host: jhub.example.com/hub/
Apr 13 12:51:57 VM02 bash[1784]: [D 2021-04-13 12:51:57.803 JupyterHub log:174] 200 GET /hub/static/js/utils.js?v=20210330215732 (@123.12.123.123) 0.65ms

I am still trying to make it work, but haven’t had any luck, so if any ideas you can share so I can explore in the meantime, I’ll much appreciate it.

Thank you!

It looks like there’s a mismatch in the host headers- for some reason the port is being dropped (different ports for the same host are considered different origins). You could try setting a X-Forward-* or Forward headers.

There was a similar sounding PR to fix a related issue in Fix #2284 must be sent from authorization page by elgalu · Pull Request #3219 · jupyterhub/jupyterhub · GitHub so there’s a chance it’s a bug, but to verify this we’d need to see the headers that Nginx is sending to JupyterHub.

Hello, thanks for your answer. Seems like this is being set in nginx configuration but still, the error persists:

How can I see and share with you the headers that are being passed?

Thank you.

The preserve-host-header config that’s important is:

proxy_set_header Host $host;