Guides
JupyterHub

JupyterHub за nginx: проксирование и SSL

Как поставить JupyterHub за nginx reverse proxy. c_JupyterHub_base_url, WebSocket проксирование (важно для Jupyter), SSL

Всем привет. Если вы разворачиваете JupyterHub для команды, рано или поздно встанете вопрос о его безопасном доступе извне. Ставить JupyterHub “голым” на 80-й порт — плохая идея. Правильный путь — вынести его за reverse proxy, например nginx, который возьмет на себя SSL termination и грамотную маршрутизацию. Это не только безопаснее, но и надежнее: nginx куда устойчивее к нагрузке и атакам. Сегодня разберем, как сделать это правильно с первого раза, избежав классических ошибок с 502 ошибками и обрывом WebSocket-соединений.

Архитектура и базовая настройка nginx

Предположим, ваш JupyterHub слушает на localhost:8000. Наша цель — проксировать все запросы с /jupyter/ на него, а SSL пусть заканчивается на nginx. Вот базовый конфиг для блока server в nginx.

server {
    listen 443 ssl http2;
    server_name hub.your-company.com;

    ssl_certificate /etc/ssl/certs/your-cert.pem;
    ssl_certificate_key /etc/ssl/private/your-key.key;

    location /jupyter/ {
        proxy_pass http://127.0.0.1:8000/;

        # Критически важные заголовки
        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_read_timeout 300s;
        proxy_connect_timeout 75s;
    }
}

Обратите внимание на слэш в конце proxy_pass. Он отрезает префикс /jupyter/ при передаче запроса на бэкенд. Без него JupyterHub получит путь, начинающийся с /jupyter/, и ничего не заработает.

Настройка JupyterHub для работы с префиксом

JupyterHub должен знать, что работает не от корня, а под префиксом. Иначе он будет генерировать неправильные ссылки. В конфигурационном файле jupyterhub_config.py укажите:

c.JupyterHub.base_url = '/jupyter/'
c.JupyterHub.bind_url = 'http://127.0.0.1:8000'

Это ключевой момент. bind_url указывает, где слушает сам хаб, а base_url — по какому пути он доступен снаружи, через nginx.

Проксирование WebSocket

JupyterLab/Notebook активно используют WebSocket для взаимодействия с ядром. Если nginx не настроить правильно, вы увидите ошибки в консоли браузера и постоянные разрывы соединения. Решение — добавить в блок location директивы для апгрейда протокола.

location /jupyter/ {
    proxy_pass http://127.0.0.1:8000/;
    # ... остальные proxy_set_header ...

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

Эти три строки заставляют nginx корректно передавать запрос на обновление протокола с HTTP на WebSocket.

Типичные грабли и их обход

502 Bad Gateway. Самая частая причина — JupyterHub не запущен или слушает на другом порту. Проверьте командой ss -tlnp | grep 8000. Вторая причина — конфликт слэшей в proxy_pass. Следуйте шаблону выше: если в location есть слэш на конце пути, в proxy_pass он тоже должен быть.

WebSocket соединяется, но сразу обрывается (status 101 Switching Protocols, затем disconnect). Почти гарантированно забыли директивы Upgrade и Connection. Добавьте их, как показано выше, и перезагрузите nginx.

Статика отдается неправильно или ссылки ведут не туда. Это ошибка конфигурации c.JupyterHub.base_url. Убедитесь, что значение точно совпадает с префиксом в nginx location, включая ведущий и завершающий слэши.

Ошибка “Origin not allowed” в логах JupyterHub. Проблема с заголовками. Убедитесь, что передаются X-Forwarded-Proto и Host. JupyterHub использует их для проверки origin.

Итог: вынос JupyterHub за nginx — стандартная и правильная практика. Главное — синхронизировать base_url и префикс location, а также не забыть про WebSocket. После настройки вы получите безопасный, централизованно управляемый доступ к вашим интерактивным средам.