Как установить и настроить Jupyter на VPS
Расчёт, который на ноутбуке идёт четыре часа, обрывается на закрытой крышке; датасет не влезает в оперативку; коллеге нужен тот же ноутбук, и вы шлёте ему .ipynb в мессенджер. Jupyter на своём сервере снимает все три проблемы разом — но выставленный наружу без настройки выдаёт любому желающему шелл под вашим пользователем. Разберём установку целиком: от venv на чистой Ubuntu до пароля, systemd-юнита и Nginx, который умеет держать вебсокеты.
Содержание
- Что вы ставите: JupyterLab, Notebook 7 или JupyterHub
- Установка на Ubuntu 24.04: venv, PEP 668 и ядра под проект
- Первый запуск: токен, пароль и SSH-туннель
- Конфиг сервера: адрес, уборка ядер и лимит вывода
- Автозапуск: systemd-юнит или контейнер
- Наружу правильно: домен, TLS и Nginx с вебсокетами
- Какой сервер взять под Jupyter в MAATRIX
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество 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 + JupyterLab | PAM или 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 -T — Out 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 и статика. Их съедает то, что вы считаете, так что конфигурацию выбирают под данные.
| Профиль | vCPU | RAM | NVMe | На что реально хватит |
|---|---|---|---|---|
| Минимум | 1 | 2 ГБ | 25 ГБ | Один человек, учебные тетрадки, скрипты по расписанию |
| Рабочий | 2 | 4 ГБ | 50 ГБ | pandas на файлах в сотни МБ, Postgres рядом, пара ядер разом |
| Комфортный | 4 | 8–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 — десятки моделей в одном окне. Оплата картой РФ и по СБП.