MAATRIX / Блог / Как установить и настроить Jupyter на VPS

Как установить и настроить Jupyter на VPS

Как установить и настроить Jupyter на VPS

MAATRIX

Расчёт, который на ноутбуке идёт четыре часа, обрывается на закрытой крышке; датасет не влезает в оперативку; коллеге нужен тот же ноутбук, и вы шлёте ему .ipynb в мессенджер. Jupyter на своём сервере снимает все три проблемы разом — но выставленный наружу без настройки выдаёт любому желающему шелл под вашим пользователем. Разберём установку целиком: от venv на чистой Ubuntu до пароля, systemd-юнита и Nginx, который умеет держать вебсокеты.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →

Что вы ставите: JupyterLab, Notebook 7 или JupyterHub

Под словом «Jupyter» прячутся четыре разных пакета — от выбора зависит и конфиг, и требования к железу.

  • jupyter_server (2.21.0) — веб-сервер: порт, авторизация, запуск ядер, API. Настройка в классе ServerApp, нужен Python 3.10+.
  • JupyterLab (4.6.3) — интерфейс: файловый браузер, вкладки, терминал. Ставят в 90 % случаев.
  • Notebook 7 (7.6.2) — не «классик», а тот же JupyterLab в виде одной страницы без панелей, с общим бэкендом.
  • JupyterHub (5.5.1) — многопользовательский доступ: логин, а за ним — отдельный процесс на каждого со своим каталогом.
ЗадачаЧто ставитьЦена вопроса
Один человек, свой серверjupyterlabОдин процесс, один порт
Привычный вид отдельной тетрадкиnotebookТо же самое, другой интерфейс
3–20 человек с личными файламиjupyterhub + JupyterLabPAM или OAuth, процесс на каждого, память × N
Ноутбуки как отчёты по расписаниюpapermill и nbconvert в cronВеб наружу не нужен вовсе

Главное перед установкой: Jupyter — это сервис удалённого выполнения кода, и так задумано. Кто попал внутрь, выполняет что угодно от имени пользователя сервера: !cat /etc/shadow, терминал, os.system. Открытые ноутбуки методично сканируют и используют под майнинг — их находят по ответу /api/status. Дальше всё вокруг трёх вещей: отдельный пользователь без прав, 127.0.0.1, наружу только через прокси с TLS.

Установка на Ubuntu 24.04: venv, PEP 668 и ядра под проект

Ubuntu 24.04 несёт Python 3.12.3, минимум для JupyterLab — 3.10. Debian 12 с 3.11 подходит, а на Ubuntu 20.04 с Python 3.8 свежий JupyterLab не встанет: pip молча подберёт версию четырёхлетней давности.

Заводим отдельного пользователя, не работаем в /root:

apt update && apt install -y python3-venv python3-pip build-essential curl
adduser --system --group --home /opt/jupyter --shell /usr/sbin/nologin jupyter
mkdir -p /opt/jupyter/{work,etc}
chown -R jupyter:jupyter /opt/jupyter

Кто по привычке наберёт pip3 install jupyterlab от root, встретит PEP 668 — с Ubuntu 23.04 системный Python защищён:

error: externally-managed-environment

× This environment is externally managed
╰─> To install Python packages system-wide, try apt install
    python3-xyz, where xyz is the package you are trying to install.

Флаг --break-system-packages — плохая идея: смешаете пакеты с системными, однажды apt upgrade не поднимет Python. Верный путь — venv:

sudo -u jupyter python3 -m venv /opt/jupyter/venv
sudo -u jupyter /opt/jupyter/venv/bin/pip install --upgrade pip wheel
sudo -u jupyter /opt/jupyter/venv/bin/pip install jupyterlab
/opt/jupyter/venv/bin/jupyter --version

Она же и первая проверка, что всё встало:

Selected Jupyter core packages...
jupyter_client   : 8.10.0
jupyter_core     : 5.9.1
jupyter_server   : 2.21.0
jupyterlab       : 4.6.3
notebook         : not installed

Голое окружение весит 150–200 МБ, с pandas и scikit-learn — уже за гигабайт, с torch — за пять. На 1–2 ГБ сборка колёс падает без объяснений: pip печатает Killed, в dmesg -TOut of memory: Killed process 4211 (cc1plus). Лечит --only-binary=:all: или swap:

fallocate -l 2G /swapfile && chmod 600 /swapfile
mkswap /swapfile && swapon /swapfile
echo '/swapfile none swap sw 0 0' >> /etc/fstab

Ядра под проект. Не тащите все библиотеки в venv Jupyter — конфликт numpy в одном проекте сломает другой. У каждого проекта — своё окружение, зарегистрированное как ядро:

python3 -m venv /opt/projects/ml/venv
/opt/projects/ml/venv/bin/pip install ipykernel pandas scikit-learn
/opt/projects/ml/venv/bin/python -m ipykernel install \
  --prefix=/opt/jupyter/venv --name ml --display-name "Python (ml)"

Проверка — jupyter kernelspec list. Она же спасает от частой претензии новичка: «поставил пакет, а тетрадь пишет ModuleNotFoundError: No module named 'pandas'» — ставили в одно окружение, а ядро работает из другого. Истину показывает import sys; print(sys.executable), а ставить надо командой %pip install pandas — она кладёт пакет в окружение ядра, а не туда, где был ssh.

Развернуть за пару минут

Готовый образ на VPS MAATRIX: NVMe, AMD EPYC, root-доступ. Локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Развернуть Portainer

Первый запуск: токен, пароль и SSH-туннель

Прежде чем оформлять сервис, запустите руками и посмотрите на вывод:

sudo -u jupyter /opt/jupyter/venv/bin/jupyter lab \
  --no-browser --ip 127.0.0.1 --port 8888 --ServerApp.root_dir=/opt/jupyter/work

В логе будет примерно это:

[I 2026-08-28 10:12:03.114 ServerApp] Serving notebooks from local directory: /opt/jupyter/work
[I 2026-08-28 10:12:03.114 ServerApp] Jupyter Server 2.21.0 is running at:
[I 2026-08-28 10:12:03.114 ServerApp] http://127.0.0.1:8888/lab?token=6a9d1fbc0c3e4a...
[I 2026-08-28 10:12:03.115 ServerApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).

Токен генерируется заново при каждом старте. Забыли или закрыли терминал — спросите сервер: jupyter server list покажет http://127.0.0.1:8888/?token=... :: /opt/jupyter/work.

От root сервер откажется: Running as root is not recommended. Use --allow-root to bypass. Флаг есть, но в проде это то, чего делать нельзя — строка с ! в тетрадке станет командой суперпользователя.

Быстрее и безопаснее всего — SSH-туннель. Порт слушает петля, снаружи его нет; пробрасываем со своей машины:

ssh -N -L 8888:127.0.0.1:8888 root@203.0.113.10

Дальше открываете http://127.0.0.1:8888/lab?token=... у себя. Для одного пользователя на этом можно остановиться — ни домена, ни TLS, ни Nginx не нужно, поверхность атаки равна поверхности SSH. Минусы честные: туннель рвётся вместе с сетью, с планшета не зайти, коллеге ссылку не дать.

Нужен доступ по домену — вместо токена удобнее пароль, он переживает рестарт:

sudo -u jupyter JUPYTER_CONFIG_DIR=/opt/jupyter/etc \
  /opt/jupyter/venv/bin/jupyter server password

Команда просит ввод дважды и пишет хэш в /opt/jupyter/etc/jupyter_server_config.json:

{
  "IdentityProvider": {
    "hashed_password": "argon2:$argon2id$v=19$m=10240,t=10,p=8$..."
  }
}

Алгоритм по умолчанию — argon2id. В jupyter_server 2.x пароль переехал в IdentityProvider, старые инструкции с c.NotebookApp.password не подойдут — писаны под код, которого больше нет. Пароль не отменяет токен сам по себе: для входа только по паролю добавьте c.IdentityProvider.token = '', но именно вместе с паролем, иначе сервер останется без авторизации вовсе.

Конфиг сервера: адрес, уборка ядер и лимит вывода

Где Jupyter ищет конфиги, покажет jupyter --paths; JUPYTER_CONFIG_DIR перебивает поиск. Создаём /opt/jupyter/etc/jupyter_server_config.py:

c.ServerApp.ip = "127.0.0.1"
c.ServerApp.port = 8888
c.ServerApp.open_browser = False
c.ServerApp.root_dir = "/opt/jupyter/work"
c.ServerApp.allow_remote_access = False
c.ServerApp.terminals_enabled = False
c.ServerApp.max_body_size = 536870912

c.MappingKernelManager.cull_idle_timeout = 3600
c.MappingKernelManager.cull_interval = 300
c.MappingKernelManager.cull_connected = False
c.MappingKernelManager.cull_busy = False

c.ZMQChannelsWebsocketConnection.iopub_data_rate_limit = 10000000

Разберём неочевидное.

root_dir ограничивает файловый браузер — выше через веб не подняться, не песочница (терминал и os.chdir ходят где хотят), но от «а что там в /etc» бережёт.

terminals_enabled = False отключает встроенный терминал. Для личного сервера можно оставить, для общего — выключайте: шелл в браузере без единой строки в auth.log.

Уборка ядер — недооценённая настройка. Открытая тетрадка держит ipykernel, который не умирает при закрытии вкладки: через неделю висит десяток ядер с DataFrame в памяти — и OOM на ровном месте. cull_idle_timeout = 3600 гасит ядро после часа простоя, cull_interval — период проверки. cull_connected и cull_busy, если включить, убьют ядро с открытой вкладкой или посреди обучения — по умолчанию оба False, и правильно.

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

IOPub data rate exceeded. The Jupyter server will temporarily stop sending output to
the client in order to avoid crashing it. To change this limit, set the config
variable `--ServerApp.iopub_data_rate_limit`.

По умолчанию лимит — 1 000 000 байт и 1000 сообщений в секунду; поднимать стоит с оглядкой — вывод летит через вебсокет и складывается в .ipynb. Ловушка версий: подсказка в тексте ошибки устарела — в jupyter_server 2.x ServerApp.iopub_data_rate_limit помечен DEPRECATED, реально работает ZMQChannelsWebsocketConnection, и по старой подсказке получите лишь предупреждение в логе.

Автозапуск: systemd-юнит или контейнер

Ручной запуск умирает вместе с ssh-сессией. Оформляем сервис в /etc/systemd/system/jupyter.service:

[Unit]
Description=JupyterLab
After=network-online.target

[Service]
Type=simple
User=jupyter
Group=jupyter
WorkingDirectory=/opt/jupyter/work
Environment=JUPYTER_CONFIG_DIR=/opt/jupyter/etc
Environment=PATH=/opt/jupyter/venv/bin:/usr/local/bin:/usr/bin:/bin
ExecStart=/opt/jupyter/venv/bin/jupyter lab
Restart=on-failure
RestartSec=5
MemoryAccounting=yes
MemoryMax=3G
NoNewPrivileges=yes
PrivateTmp=yes

[Install]
WantedBy=multi-user.target
systemctl daemon-reload && systemctl enable --now jupyter
systemctl show jupyter -p MemoryCurrent
journalctl -u jupyter -f

MemoryMax — не бюрократия, а страховка. Без него ядро, сожравшее память, роняет вместе с собой весь сервер; с лимитом cgroup убивает только процесс ядра. Увидите честное «Kernel Restarting: The kernel appears to have died. It will restart automatically», в dmesg -T — строку про Out of memory, а сервер останется живым. Ставьте лимит на две трети физической памяти.

Вариант с Docker. Если Docker уже на сервере, тетрадки удобнее держать контейнером — весь стек внутри, без возни с системным Python. Образы переехали на Quay.io, теги на Docker Hub больше не обновляются:

services:
  jupyter:
    image: quay.io/jupyter/scipy-notebook:latest
    container_name: jupyter
    restart: unless-stopped
    ports:
      - "127.0.0.1:8888:8888"
    environment:
      JUPYTER_TOKEN: ${JUPYTER_TOKEN}
      RESTARTABLE: "yes"
    volumes:
      - ./work:/home/jovyan/work
    mem_limit: 3g

Две грабли. Первая: в контейнере работает jovyan с UID 1000 и GID 100, а смонтированный каталог обычно принадлежит root — сохранение тетрадки даёт Permission denied. Лечится заранее: chown -R 1000:100 ./work. Вторая серьёзнее: ufw не защищает опубликованные порты Docker — правила публикации пишутся в цепочки раньше очереди ufw, и ufw deny 8888/tcp не закрывает ничего, порт открыт всему интернету вместе с вашим шеллом. Спасает только явная привязка 127.0.0.1:8888:8888, как выше; проверяйте результат: ss -tlnp | grep 8888 должен показать 127.0.0.1, а не 0.0.0.0. Управлять стеком удобно через Portainer — compose кладётся в веб-редакторе, логи и рестарт в два клика.

Наружу правильно: домен, TLS и Nginx с вебсокетами

Нужен постоянный доступ из браузера — направьте A-запись поддомена на IP сервера и выпустите сертификат:

apt install -y nginx certbot python3-certbot-nginx
certbot --nginx -d lab.example.com

Прокси для Jupyter отличается от типового — ядро общается с браузером по вебсокету:

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

server {
    listen 443 ssl;
    http2 on;
    server_name lab.example.com;

    ssl_certificate     /etc/letsencrypt/live/lab.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/lab.example.com/privkey.pem;

    client_max_body_size 512m;

    location / {
        proxy_pass http://127.0.0.1:8888;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        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_buffering off;
        proxy_read_timeout 86400;
    }
}

Важно и почему это частое место провала:

  • Блок map, заголовки Upgrade/Connection. Без них ядро не подключится: висит «Connecting to kernel», в консоли — WebSocket connection to 'wss://lab.example.com/api/kernels/.../channels?session_id=...' failed.
  • proxy_read_timeout 86400. Дефолтные 60 секунд рвут простаивающий вебсокет — ядро теряется ровно тогда, когда вы отошли за кофе.
  • proxy_set_header Host $host. Без этого сервер пишет в журнал: Blocking Cross Origin API request for /api/kernels. Origin: https://lab.example.com, Host: 127.0.0.1:8888. Не чините c.ServerApp.allow_origin = '*' — так откроете API любому сайту.
  • allow_remote_access. С доменным Host защита от DNS rebinding пишет Blocking request with non-local 'Host' lab.example.com. Включаем True — обращения всё равно идут только с петли.
  • client_max_body_size. Загрузка датасета упирается сначала в Nginx (413 Request Entity Too Large), потом в ServerApp.max_body_size. Гигабайты быстрее лить rsync или scp.

Живёт Jupyter на подпути вроде /jupyter/ — обязателен c.ServerApp.base_url = "/jupyter/", иначе белый экран и 404 на статике. Файрвол — предельно скучный: ufw allow 22/tcp, ufw allow 80,443/tcp, ufw enable, порт 8888 наружу не смотрит вовсе. Подробности — в статьях про Let's Encrypt на VPS и Nginx как реверс-прокси.

Какой сервер взять под Jupyter в MAATRIX

Сам Jupyter почти не потребляет ресурсов — веб-сервер на Tornado и статика. Их съедает то, что вы считаете, так что конфигурацию выбирают под данные.

ПрофильvCPURAMNVMeНа что реально хватит
Минимум12 ГБ25 ГБОдин человек, учебные тетрадки, скрипты по расписанию
Рабочий24 ГБ50 ГБpandas на файлах в сотни МБ, Postgres рядом, пара ядер разом
Комфортный48–16 ГБ100+ ГБJupyterHub на 3–5 человек, датасеты в гигабайты, долгие расчёты

Честно про нижнюю границу. На 1 ГБ pip install pandas соберёт колесо в OOM, а read_csv на сотню мегабайт добьёт остальное — CSV в памяти весит в разы больше своего веса на диске. Гигабайт — это «посмотреть», рабочее место начинается с двух, с научным стеком — с четырёх; venv с torch и пара датасетов съедают 20 ГБ диска незаметно.

Про GPU без иллюзий. VPS — это CPU, видеокарты нет и не будет. Небольшие модели инферить можно, но потолок известен заранее: на нашем стенде (AMD EPYC 9554, 16 vCPU, Ollama 0.33.1) qwen2.5:7b в Q4_K_M даёт около 7,6 токена в секунду и 5,1 ГБ памяти. Для гипотезы в тетрадке нормально, для обучения — нет: fine-tuning и диффузия — это выделенный сервер с GPU, другая цена.

Локация — Великобритания, Лондон. Разумный выбор для аналитики: сервер тянет пакеты с PyPI, датасеты с Hugging Face и Kaggle, ходит в европейские API, а RTT Москва — Лондон обычно 50–70 мс, для веб-интерфейса неощутимо. Британский адрес не упирается в региональные ограничения, в отличие от российского. Данные россиян под 152-ФЗ держите на RU-площадке, Jupyter поднимайте рядом; если нагрузка — вызовы OpenAI или Anthropic, логичнее US, Нью-Йорк.

Про установку начистоту. Jupyter в каталоге apps.maatrix.io нет — сервер приезжает чистым, ставится по инструкции выше: около десяти минут от adduser до HTTPS. Зато в каталоге есть Portainer — разворачивается автоматически при заказе, доступы в кабинете, и в нём удобно поднять контейнер с тетрадками из compose-файла выше. Оплата — картой российского банка, СБП, криптовалютой или токеном MAAT; иностранная карта не нужна, хотя сервер в Лондоне. Память расширяется без переезда, разбор поломок — в статье про частые ошибки Jupyter на сервере.

Развернуть за пару минут

Готовый образ на VPS MAATRIX: NVMe, AMD EPYC, root-доступ. Локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Развернуть Portainer

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →

Частые вопросы

Ставить JupyterHub или хватит одного JupyterLab на всех?

Один JupyterLab на всех — общий пользователь и общая история: любой видит и может стереть чужое. Людей больше двух и файлы у каждого свои — ставьте JupyterHub 5.5.1: отдельный процесс на пользователя, но и памяти кратно числу сессий.

Ноутбук работает, а после закрытия ssh расчёт обрывается. Почему?

Jupyter запущен вручную из сессии — оформите systemd-сервисом, как в разделе выше, тогда процесс переживает разрыв связи. Расчёты идут часами без вывода — поднимите cull_idle_timeout: занятые ядра он не трогает, а паузу после завершения — да.

Можно пустить Jupyter наружу просто с паролем, без домена и TLS?

Технически да, практически — нет: по HTTP пароль и cookie сессии уходят открытым текстом, а на голый IP сертификат Let's Encrypt не выпустить. Либо SSH-туннель, либо домен с сертификатом — третьего безопасного пути нет.

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.