☰
05.11.2026

Как запустить JupyterHub на VPS

Введение

Jupyter-ноутбуки давно стали стандартным инструментом для анализа данных, экспериментов с машинным обучением, обучения и быстрого прототипирования. Запускать Jupyter на ноутбуке удобно, пока вы работаете в одиночку. Проблемы начинаются, когда то же окружение нужно команде или учебной группе: каждый ставит свой Python, версии библиотек расходятся, ноутбук, который работает у одного, падает у другого, а тяжёлые вычисления на часы занимают личный компьютер.

Эту задачу решает JupyterHub. Это многопользовательский сервер, который выдаёт каждому человеку собственное окружение Jupyter в браузере, а все эти окружения работают на одной общей машине с одинаковым набором программ и данных. Пользователи входят по логину и паролю, а администратор управляет учётными записями, ресурсами и установленными пакетами в одном месте.

В этой статье мы разберём, как устроен JupyterHub, когда его имеет смысл запускать на виртуальном сервере, и пошагово установим его на Ubuntu 24.04: от виртуального окружения Python и службы systemd до обратного прокси Nginx с бесплатным HTTPS-сертификатом и автоматической остановкой неактивных сессий.

Основные термины

Как работает JupyterHub

JupyterHub состоит из трёх взаимодействующих частей: Hub, прокси и однопользовательских серверов. Команда jupyterhub запускает Hub и по умолчанию поднимает прокси как дочерний процесс.

Типичная сессия выглядит так:

  1. Пользователь открывает адрес сервера в браузере. Запрос попадает на прокси, который по умолчанию слушает порт 8000.
  2. Прокси передаёт запрос в Hub, и тот показывает страницу входа.
  3. Аутентификатор проверяет логин и пароль. При PAM-аутентификации это данные учётной записи пользователя в Linux на сервере.
  4. После успешного входа спаунер запускает для пользователя отдельный сервер Jupyter под его учётной записью и в его домашнем каталоге.
  5. Hub сообщает прокси новый маршрут: все запросы к /user/<username>/ теперь идут напрямую на сервер этого пользователя.
  6. Пользователь работает в JupyterLab. Трафик ноутбуков, ядер и терминалов идёт через прокси к пользовательскому серверу, минуя Hub.

Внутри системы Hub слушает 127.0.0.1:8081 для своего REST API, а прокси открывает API управления на 127.0.0.1:8001. Оба порта остаются локальными, и открывать их в интернет нельзя. Пользователи видят только публичный порт прокси или порт Nginx, который стоит перед ним.

Поскольку каждый пользовательский сервер работает под отдельной учётной записью Linux, пользователи не могут читать чужие файлы, если вы явно этого не разрешите. При этом у них общая установка Python, общие пакеты и общие ресурсы сервера. Именно этим JupyterHub удобен для команд: библиотеку достаточно установить один раз, и она появится у всех.

Почему Hub работает от root

В стандартной конфигурации Hub нужны права root по двум причинам: PAM-аутентификатор читает системные данные о паролях, а спаунер должен запускать процессы от имени других пользователей. Сами пользовательские серверы работают без root. Если запуск Hub от root в вашей среде недопустим, JupyterHub можно настроить с sudospawner или со спаунерами на основе контейнеров, но эти варианты выходят за рамки статьи.

Сценарии использования и выбор конфигурации сервера

JupyterHub на виртуальном сервере хорошо подходит, когда небольшой или средней группе нужно общее окружение, доступное круглосуточно:

Требования к ресурсам зависят от числа одновременно работающих пользователей и от нагрузки. Каждый активный пользовательский сервер с запущенным ядром Python в простое занимает примерно 100–300 МБ оперативной памяти, а после загрузки данных в память гораздо больше. Для начала можно ориентироваться на такую таблицу:

Сценарий Одновременных пользователей vCPU RAM Диск
Небольшая команда, лёгкий анализ 2–5 2 4 ГБ 40 ГБ
Воркшоп или учебная группа 10–20 4 8–16 ГБ 80 ГБ
Машинное обучение, большие датасеты 5–10 8+ 32 ГБ+ 160 ГБ+

 

Преимущество VPS в облаке Serverspace в том, что размер не нужно угадывать заранее: можно начать со скромной конфигурации и добавлять процессоры, память или диск по мере роста числа пользователей.

Для крупных инсталляций на десятки и сотни пользователей проект JupyterHub поддерживает отдельный дистрибутив для Kubernetes. Для одного сервера описанная ниже схема проще и легче в сопровождении.

Установка JupyterHub на VPS: пошаговая инструкция

Мы установим JupyterHub с JupyterLab на Ubuntu 24.04, разместим его в изолированном виртуальном окружении Python в каталоге /opt/jupyterhub, запустим как службу systemd и опубликуем через Nginx с HTTPS-сертификатом от Let's Encrypt.

Что понадобится

Шаг 1. Обновите систему и установите зависимости

Подключитесь к серверу по SSH и обновите списки пакетов и установленные пакеты:

sudo apt update && sudo apt upgrade -y

Установите инструменты Python, Node.js с npm (они нужны для прокси) и Nginx:

sudo apt install -y python3 python3-venv python3-pip nodejs npm nginx

Проверьте установленные версии:

python3 --version
node --version

На Ubuntu 24.04 вывод выглядит так:

Python 3.12.3
v18.19.1

Шаг 2. Установите configurable-http-proxy

Прокси распространяется как пакет npm. Установите его глобально, чтобы команда configurable-http-proxy была доступна во всей системе:

sudo npm install -g configurable-http-proxy

Убедитесь, что команда находится:

which configurable-http-proxy

Что делать, если команда не найдена

Глобальные пакеты npm обычно ставятся в /usr/local/bin. Если which ничего не вывела, посмотрите вывод npm prefix -g и добавьте соответствующий каталог bin в переменную PATH в unit-файле systemd, который мы создадим на шаге 6.

Шаг 3. Создайте виртуальное окружение и установите JupyterHub

Мы держим JupyterHub отдельно от системного Python, чтобы обновления ОС его не ломали, а он не мешал другим программам. Создайте виртуальное окружение:

sudo python3 -m venv /opt/jupyterhub

Установите в него JupyterHub, JupyterLab и классический интерфейс Notebook. Пакет jupyterhub-idle-culler понадобится позже для остановки неактивных сессий:

sudo /opt/jupyterhub/bin/python3 -m pip install --upgrade pip
sudo /opt/jupyterhub/bin/python3 -m pip install jupyterhub jupyterlab notebook jupyterhub-idle-culler

Проверьте версию:

/opt/jupyterhub/bin/jupyterhub --version

Команда выведет номер установленной версии, например 5.3.0. Точное значение зависит от актуального релиза.

Установите в то же окружение библиотеки, которые нужны пользователям, например:

sudo /opt/jupyterhub/bin/python3 -m pip install numpy pandas matplotlib scikit-learn

Установленные так пакеты доступны всем пользователям, потому что все пользовательские серверы запускаются из /opt/jupyterhub.

Шаг 4. Создайте учётные записи пользователей

PAM-аутентификатор пускает пользователей по их учётным записям Linux, поэтому каждому пользователю JupyterHub нужен системный пользователь. Для примера создадим две учётные записи: администратора alice и обычного пользователя bob:

sudo adduser alice
sudo adduser bob

Команда adduser создаёт домашний каталог и запрашивает пароль. Именно этот пароль пользователь будет вводить на странице входа JupyterHub. Задавайте надёжные пароли: страница входа будет доступна из интернета.

Шаг 5. Настройте JupyterHub

Создайте каталог для конфигурации и сгенерируйте файл настроек по умолчанию:

sudo mkdir -p /opt/jupyterhub/etc/jupyterhub
cd /opt/jupyterhub/etc/jupyterhub
sudo /opt/jupyterhub/bin/jupyterhub --generate-config

Команда создаст файл jupyterhub_config.py, в котором все доступные параметры закомментированы. Это обычный файл на Python. Откройте его в редакторе:

sudo nano /opt/jupyterhub/etc/jupyterhub/jupyterhub_config.py

Добавьте в конец файла следующие строки:

import sys

# Слушать только localhost: публичный трафик обслуживает Nginx
c.JupyterHub.bind_url = 'http://127.0.0.1:8000'

# После входа открывать JupyterLab вместо классического интерфейса
c.Spawner.default_url = '/lab'

# Пользователи, которым разрешён вход
c.Authenticator.allowed_users = {'alice', 'bob'}

# Пользователи с доступом к панели администратора
c.Authenticator.admin_users = {'alice'}

# Останавливать пользовательские серверы после часа бездействия
c.JupyterHub.services = [
{
'name': 'idle-culler',
'command': [sys.executable, '-m', 'jupyterhub_idle_culler', '--timeout=3600'],
}
]
c.JupyterHub.load_roles = [
{
'name': 'idle-culler',
'scopes': ['list:users', 'read:users:activity', 'read:servers', 'delete:servers'],
'services': ['idle-culler'],
}
]

Что делают эти настройки:

Храните служебные файлы в отдельном каталоге

При первом запуске JupyterHub создаёт в рабочем каталоге файлы jupyterhub.sqlite (база пользователей) и jupyterhub_cookie_secret (ключ для подписи cookie). В нашей схеме это каталог /opt/jupyterhub/etc/jupyterhub. Не публикуйте эти файлы и включите их в резервные копии.

Шаг 6. Создайте службу systemd

Чтобы JupyterHub запускался автоматически при загрузке и перезапускался после сбоев, создайте unit-файл systemd:

sudo nano /etc/systemd/system/jupyterhub.service

Вставьте в него следующее содержимое:

[Unit]
Description=JupyterHub
After=network.target

[Service]
User=root
Environment="PATH=/opt/jupyterhub/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
WorkingDirectory=/opt/jupyterhub/etc/jupyterhub
ExecStart=/opt/jupyterhub/bin/jupyterhub -f /opt/jupyterhub/etc/jupyterhub/jupyterhub_config.py
Restart=on-failure

[Install]
WantedBy=multi-user.target

Переменная PATH ставит /opt/jupyterhub/bin на первое место. Это важно: спаунер ищет команду jupyterhub-singleuser в PATH, а пользовательские серверы будут использовать Python и пакеты из виртуального окружения.

Перечитайте конфигурацию systemd, включите службу и запустите её:

sudo systemctl daemon-reload
sudo systemctl enable --now jupyterhub

Проверьте статус:

sudo systemctl status jupyterhub

Служба должна быть в состоянии active (running). Чтобы следить за журналом в реальном времени, выполните:

sudo journalctl -u jupyterhub -f

Среди сообщений при запуске должна появиться строка примерно такого вида:

[I 2026-10-18 10:15:42.318 JupyterHub app:3770] JupyterHub is now running at http://127.0.0.1:8000/

Время и номер строки в исходном файле у вас будут другими. Чтобы выйти из просмотра журнала, нажмите Ctrl+C.

Шаг 7. Настройте Nginx как обратный прокси

JupyterLab активно использует WebSocket для связи с ядрами, поэтому конфигурация Nginx должна передавать заголовки Upgrade и Connection. Создайте конфигурацию сайта:

sudo nano /etc/nginx/sites-available/jupyterhub

Добавьте следующее, заменив hub.example.com на свой домен:

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

server {
listen 80;
server_name hub.example.com;

client_max_body_size 100M;

location / {
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_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_read_timeout 86400;
}
}

Несколько пояснений к конфигурации:

Включите сайт, отключите сайт по умолчанию, проверьте синтаксис и перезагрузите Nginx:

sudo ln -s /etc/nginx/sites-available/jupyterhub /etc/nginx/sites-enabled/
sudo rm /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl reload nginx

Если конфигурация верна, nginx -t выведет:

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Шаг 8. Включите HTTPS с Let's Encrypt

На странице входа пользователи вводят пароли, поэтому соединение должно быть зашифровано. Установите Certbot с плагином для Nginx:

sudo apt install -y certbot python3-certbot-nginx

Запросите сертификат для своего домена:

sudo certbot --nginx -d hub.example.com

Certbot спросит адрес электронной почты для уведомлений об истечении срока, получит сертификат, добавит настройки HTTPS в конфигурацию Nginx и настроит перенаправление с HTTP на HTTPS. Продление выполняется автоматически по таймеру systemd. Проверить его можно пробным запуском:

sudo certbot renew --dry-run

Шаг 9. Настройте файрвол

Разрешите только SSH и веб-трафик, а всё остальное, включая порт 8000, оставьте закрытым:

sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable

Разрешайте SSH до включения файрвола, иначе можно потерять доступ к серверу. Проверьте результат:

sudo ufw status
Status: active

To Action From
-- ------ ----
OpenSSH ALLOW Anywhere
Nginx Full ALLOW Anywhere
OpenSSH (v6) ALLOW Anywhere (v6)
Nginx Full (v6) ALLOW Anywhere (v6)

Шаг 10. Войдите и проверьте работу

Откройте в браузере https://hub.example.com. Появится страница входа JupyterHub. Войдите как alice с паролем, заданным на шаге 4. Через несколько секунд JupyterHub запустит личный сервер, и откроется JupyterLab с содержимым домашнего каталога пользователя.

Создайте новый ноутбук и выполните простую проверку:

import sys, getpass
print(getpass.getuser())
print(sys.executable)

Ожидаемый вывод подтверждает, что сервер работает под учётной записью пользователя и использует общее виртуальное окружение:

alice
/opt/jupyterhub/bin/python3

Поскольку alice администратор, в меню File → Hub Control Panel ей доступна страница Admin. Там видно, какие пользователи активны, можно запускать и останавливать их серверы и добавлять новых пользователей.

Как добавить пользователя позже

Чтобы добавить пользователя, создайте учётную запись Linux командой adduser, впишите имя в allowed_users в файле jupyterhub_config.py и перезапустите службу командой sudo systemctl restart jupyterhub. Учтите, что в этой схеме перезапуск останавливает и серверы пользователей, которые в этот момент работают: JupyterLab автоматически сохраняет ноутбуки раз в пару минут, но последние несохранённые правки и все переменные в запущенных ядрах теряются, поэтому планируйте перезапуски на спокойное время.

Ограничение ресурсов для пользователей

Со стандартным LocalProcessSpawner один пользователь может занять весь процессор и всю память сервера, например загрузив огромный датасет. Параметры mem_limit и cpu_limit в JupyterHub есть, но этот спаунер их игнорирует. Если нужны жёсткие лимиты, установите в виртуальное окружение пакет jupyterhub-systemdspawner и задайте c.JupyterHub.spawner_class = 'systemdspawner.SystemdSpawner'. Этот спаунер запускает каждый пользовательский сервер как отдельный unit systemd и применяет ограничения памяти и процессора через cgroups. Другой вариант — DockerSpawner, который изолирует каждого пользователя в контейнере.

Решение типичных проблем

Заключение

Мы развернули JupyterHub на одном виртуальном сервере: установили его в изолированное окружение Python, настроили вход по системным учётным записям, автозапуск через systemd, публикацию через Nginx с HTTPS и автоматическую остановку неактивных сессий. В результате каждый участник команды получает личный JupyterLab в браузере, а библиотеки, данные и вычислительные мощности управляются из одного места.

Такая схема хорошо подходит для команд из нескольких человек, учебных групп и исследовательских коллективов. С ростом нагрузки логичные следующие шаги — лимиты ресурсов через SystemdSpawner, единый вход через OAuth-аутентификатор и регулярное резервное копирование домашних каталогов. VPS от Serverspace позволяет масштабировать сервер вместе с нагрузкой: можно начать с небольшой конфигурации и добавлять ресурсы, когда они понадобятся пользователям.