在实验室里,经常会有人拎着笔记本过来问:服务器上怎么开 Jupyter Notebook?最好还能直接用我自己配好的那个虚拟环境。这个问题听起来简单,真正上手才发现至少有四道坎:远程连接要通、Jupyter 要在没显示屏的服务器上跑起来、本地浏览器要能正常打开页面、最后虚拟环境还得能被 Notebook 识别,不然一新建文件又回到默认的 base,前几分钟全白折腾。我见过不少人在前两步就卡住了,更别说最后一步——环境装了一堆,打开 Notebook 却死活找不到。
这篇文章我就把整套流程完整捋一遍。假设你已经在某个实验室的服务器上有账号,服务器是 Linux 系统,你平时主要通过 SSH 登录。看完之后你不仅能跑起 Jupyter,还能把自定义环境加到内核列表里,彻底告别"嗯?怎么还是 base"的尴尬。
1. 先看运行链路:浏览器只是个遥控器
1.1 Jupyter 到底跑在哪里
很多刚接触服务器 Jupyter 的人会误以为 Notebook 运行在自己电脑上。实际上,你在本地浏览器里看到的网页只是前端界面,真正执行 Python 代码的是服务器上的 kernel 进程。也就是说,所有的 import、计算、模型训练都发生在服务器上,你的笔记本只负责展示页面、收集输入和显示结果。
拆开看这条链路是这样的:
- 服务器上启动 Jupyter Server,监听某个端口(默认 8888)。
- 本地浏览器通过 HTTP 访问服务器的端口,Jupyter 把 Notebook 页面推送到浏览器。
- 你在单元格里按 Ctrl+Enter,代码被发送回服务器,交给 kernel 执行。
- 执行结果(包括 print、图片、表格)再通过网络返回浏览器显示。
理解这一点非常关键,因为你之后遇到的所有问题,比如"连不上""打开慢""内核断连",本质上都在上面这条链路的不同环节出了问题。只要定位到是哪一环断掉,排查思路就清晰了。
1.2 动手前必须确认的三件事
我建议在安装任何东西之前,先检查三件事,避免后面白忙活:
- 服务器 IP 或主机名是否可用。用
ssh username@server_ip能正常登录。 - 你的账号是否有写权限,至少在家目录里有空间装包、建环境。
- Python 是否已安装。一般服务器都会装,但版本可能旧一点。
再补充一个我踩过的真实小坑:有一次某同事让我帮他在服务器上配 Jupyter,折腾半天发现他根本连不上服务器,最后查出来是 SSH 登录时把端口写错了。如果 SSH 这一步都不通,后面所有步骤都无从谈起。所以别急着装软件,先确认"能连上"这个前提。
可以用下面命令快速确认 Python 环境:
which python3 python3 --version如果系统里有多个 Python,建议记下路径。等会儿建虚拟环境时要用。
2. 创建虚拟环境:把依赖隔离在自己的小房间
2.1 为什么不能直接装在系统 Python 里
实验室服务器通常是多人共用的,系统 Python 环境往往已经被折腾得惨不忍睹。A 研究组需要 pytorch 2.x,B 研究组还在用 1.x,C 同学装了个不知名的包把 numpy 直接升级到不兼容的版本,结果大家写代码的时候全都一脸懵。
虚拟环境就是为了解决这种"一个环境装所有东西导致的混乱"。你可以把它理解成合租房里的独立房间:公共区域(系统 Python)保持不变,每个房间(虚拟环境)里的软件随便折腾,互不干扰。你只需要在干活的时候进自己的房间,干完再出来就行。
2.2 使用 conda 创建环境
如果你的服务器已经装了 Anaconda 或 Miniconda,最简单的方式就是用 conda。Miniconda 更轻量,Anaconda 自带很多常用库。没有的话可以用 Miniconda 安装脚本装一个。
创建环境:
conda create -n myenv python=3.10这里myenv是环境名,python=3.10指定解释器版本。conda 的优点是能管理 Python 版本本身,比如系统默认 Python 是 3.8,但你项目需要 3.10,conda 可以直接给你一个独立的 3.10。
激活环境:
conda activate myenv激活后命令行前面会出现(myenv)标记,这时候which python指向的就是环境内的解释器了。
如果提示conda activate不能用,可能需要先跑一次:
conda init bash然后重新登录 SSH,或者重新加载 shell 配置。这个坑很常见,尤其在新装的 Miniconda 上。
2.3 使用 venv 创建环境
如果不想装 Anaconda,用系统自带的venv也完全够用。它更轻量,但只能基于系统当前的 Python 版本创建环境,不能随意切换 Python 大版本。
cd ~ python3 -m venv myenv source myenv/bin/activate激活后(myenv)前缀同样会出现。venv 的好处是不依赖额外工具,干净利落;缺点是如果你需要多个 Python 版本,它没法直接解决。
两种方式对比:
| 对比项 | conda | venv |
|---|---|---|
| Python 版本切换 | 支持,可指定任意版本 | 不支持,跟随系统 Python |
| 底层库管理 | 可管理部分非 Python 依赖 | 只管理 Python 包 |
| 安装体积 | 较大,Miniconda 也有几百 MB | 轻量 |
| 适用场景 | 深度学习、需要不同 Python 版本 | 普通项目、只需要隔离 Python 包 |
我在实验室实际用得最多的还是 conda,因为深度学习项目经常需要指定 CUDA 版本配合的 Python 版本,venv 做不到这一点。如果只是写脚本做数据分析,venv 就够了。
3. 在服务器上安装并配置 Jupyter
3.1 安装 Jupyter Notebook
创建好环境之后,先激活环境,然后在环境内部安装 Jupyter:
conda activate myenv pip install jupyter notebook如果你用的是 JupyterLab,也可以:
pip install jupyterlab为什么强调"激活后再安装"?因为这样 Jupyter 相关包会装进当前环境,而不是污染 base。后面启动时用当前环境的jupyter命令,路径才不会搞混。
注意区分一下版本:现在 Jupyter Notebook 有 7.x 和旧版 6.x。7.x 基于 Jupyter Server,配置项名字和旧版略有差异。如果你看到网上教程写的c.NotebookApp.xxx不生效,大概率是因为你在 7.x 上找了旧配置项。后面我再详细说配置差异。
安装完成后,可以先跑一下:
jupyter --version确认安装成功,并输出版本信息。
3.2 生成配置文件
Jupyter 启动时会读一个配置文件,通常是~/.jupyter/jupyter_notebook_config.py。第一次运行需要手动生成:
jupyter notebook --generate-config然后编辑这个文件。对于 Jupyter Notebook 7.x,主要是这一段:
c.ServerApp.ip = '127.0.0.1' # 只监听本机地址 c.ServerApp.port = 8888 # 使用端口 8888 c.ServerApp.open_browser = False # 服务器上不自动打开浏览器 c.ServerApp.allow_remote_access = True # 允许远程访问(配合端口转发)如果是旧版 6.x,对应的是:
c.NotebookApp.ip = '127.0.0.1' c.NotebookApp.port = 8888 c.NotebookApp.open_browser = False c.NotebookApp.allow_remote_access = True我个人建议新装机器直接用 7.x,因为它内部切换到 Jupyter Server 之后,扩展机制和稳定性都更现代。旧版配置虽然兼容但迟早要迁。
ip设置成127.0.0.1而不是0.0.0.0,是为了安全考虑。除非你明确知道自己在做什么,否则不要直接把服务暴露到所有网卡上。后续远程访问用 SSH 端口转发就够了,没必要监听公网。后面第 4 节我会展开讲。
3.3 设置登录密码
由于 Jupyter 自带认证机制,最好用一个密码保护。旧版 Jupyter 提供命令:
jupyter notebook password新版同样支持这个命令,它会提示你输入并确认密码。执行后密码会被哈希写入~/.jupyter/jupyter_server_config.json,以后启动 Notebook 时只需要输入这个密码。
为什么不推荐启动时用 token?因为每次重启 token 都会变,然后你得去日志里找那一长串字符,非常麻烦。设一个固定密码,一劳永逸。密码设置好之后,网页登录只会要求输这个密码,不会再弹 token 框。
4. 远程访问:两种常用方案对比
4.1 方案一:SSH 端口转发(我最推荐)
这个方案安全,也最容易排查。原理很简单:你在本地电脑和服务器之间建立一条 SSH 加密通道,把服务器上的某个端口"映射"到本地的一个端口。这样浏览器访问本地端口,数据通过 SSH 隧道转发到服务器上的 Jupyter。
命令格式:
ssh -N -f -L 8888:localhost:8888 username@server_ip各个参数的含义:
-N表示不执行远程命令,只做转发-f表示后台运行-L 8888:localhost:8888表示把本机的 8888 端口映射到服务器的 8888 端口
命令执行完之后,在你本地浏览器打开:
http://localhost:8888就能看到 Jupyter 登录页面了。
这个方法的好处是:服务器上的 Jupyter 只监听127.0.0.1,外部网络根本访问不到它,数据在传输过程中也被 SSH 加密。即使所有人共用服务器,也不用担心别人在网络上抓到你的密码。
如果 SSH 连接容易断开,可以加个保活参数:
ssh -N -f -o ServerAliveInterval=60 -L 8888:localhost:8888 username@server_ipServerAliveInterval=60的意思是每 60 秒发一次心跳包,防止长时间没流量被防火墙掐断。我实测下来,加了这项之后连续挂一两天都没问题。
Windows 用户如果用的是 PowerShell 或者 Windows Terminal,直接敲同样的命令即可。如果你用的是 MobaXterm 这类工具,它自带隧道配置界面,图形化操作也可以。
4.2 方案二:直接开放端口访问
还有一种方式是让 Jupyter 监听0.0.0.0,然后浏览器直接访问服务器的 IP 加端口:
http://服务器IP:8888需要改配置:
c.ServerApp.ip = '0.0.0.0'改完之后别忘了检查防火墙。很多服务器的防火墙默认没开 8888 端口,需要放行:
sudo ufw allow 8888某些集群环境还有安全组设置,得在管理页面里放行端口。
这个方法适合什么场景?适合你确定服务器处于可信内网、且你能方便地开放端口的情况。但说实话风险比较大:Jupyter 有执行任意命令的能力,一旦有人猜到密码或利用漏洞,等于直接把服务器控制权交出去。所以我基本只在自己有完全掌控权的机器上用,实验室共用服务器我建议大家走 SSH 端口转发。
4.3 启动 Jupyter 并在后台运行
一切配置好之后,启动命令很简单:
jupyter notebook --no-browser --port=8888--no-browser告诉服务器不要尝试打开浏览器,毕竟服务器上根本没有图形界面。
问题是,如果直接在前台运行,SSH 窗口一关,Jupyter 也就停了。所以要让它一直在后台跑,两个办法比较靠谱:
方法一,用 nohup:
nohup jupyter notebook --no-browser --port=8888 > ~/jupyter.log 2>&1 &这样 Jupyter 在后台跑,日志输出到~/jupyter.log,以后想看错误就去翻这个文件。
方法二,用 tmux,更灵活:
tmux new -s jupyter jupyter notebook --no-browser --port=8888进入 tmux 后跑 Jupyter,按Ctrl-b d退出会话,SSH 断开也不影响。下次回来:
tmux attach -t jupyter就能回到 Jupyter 的会话界面。如果你想关掉 Jupyter,在 tmux 里按Ctrl-c即可。
我个人现在基本只用 tmux,因为除了跑 Jupyter,我平时在服务器上跑训练脚本也靠它。如果你完全不想学新工具,nohup 也能满足 90% 的需求。
另一个小习惯:启动后别急着关终端,先看日志里是否有报错。正常时日志最后会显示类似:
http://127.0.0.1:8888/如果这里显示的不是127.0.0.1而是别的地址,说明配置里 IP 没有改对。要是启动失败,日志里一般会直接告诉你原因,十有八九是端口被占了。
5. 关键一步:让 Jupyter 认得出你的虚拟环境
5.1 为什么 Notebook 里还是默认环境
很多人在服务器上装好了虚拟环境,也激活了环境,启动 Jupyter,然后新建 Notebook 一看,sys.executable指向的还是系统的 Python 路径,装了一堆包的环境好像根本没生效。
原因在于:Jupyter 启动时会根据它自己的运行环境去调用 Python kernel。你虽然在 shell 里激活了myenv,但 Jupyter Server 进程本身的 Python 解释器可能还是 base 环境的。更关键的是,Notebook 的 kernel 是一个独立进程,你在 shell 里 activate 的环境对它没有任何直接影响。
就好比你订了酒店房间,但酒店前台给你的房卡却是公共区域的卡,你得单独告诉 Jupyter"现在我要用这个环境的 Python 作为 kernel"。
5.2 使用 ipykernel 注册环境
解决办法是让每个虚拟环境把自己"注册"成 Jupyter 的一个可选内核。这样启动 Notebook 之后就能在下拉菜单里看到它,点一下就能切换。
操作分两步:
第一步,在虚拟环境里安装 ipykernel:
conda activate myenv pip install ipykernel第二步,注册内核:
python -m ipykernel install --user --name=myenv --display-name="MyEnv Python 3.10"参数解释:
--name=myenv是内核的内部名字,用来唯一标识。--display-name是你在 Jupyter 网页端下拉菜单里看到的显示名,可以随便起,建议起得让自己一眼能认出来。--user表示把内核配置安装到当前用户目录,通常在~/.local/share/jupyter/kernels/下,不需要 root 权限。
注册之后,刷新 Jupyter 网页。在新建 Notebook 的时候,Kernel 菜单里就能看到 "MyEnv Python 3.10" 了。已打开的 Notebook 也可以在菜单栏Kernel -> Change Kernel里切换。
注册完可以验证一下,新建选这个内核的 Notebook,运行:
import sys print(sys.executable)如果输出的是~/miniconda3/envs/myenv/bin/python或者类似的路径,说明环境已经正确穿透到 kernel 里了。这个验证习惯我强烈建议养成,每次配完环境都看一眼,能省不少事。
5.3 多个环境怎么管理
如果你经常给不同项目建环境,会发现每次都要重复一遍pip install ipykernel和python -m ipykernel install。这个过程可以写成一个简单脚本:
#!/bin/bash conda activate $1 pip install ipykernel python -m ipykernel install --user --name=$1 --display-name="$1"调用时bash add_kernel.sh myenv就行。有段时间我实验室里帮好几个同学配环境,有了这个脚本,一套流程 30 秒搞定。
还有一个更省事的思路:直接装一个nb_conda_kernels包,它会在启动 Jupyter 时自动扫描所有的 conda 环境,把它们全部列成可选内核。装一次,以后建新环境直接出现在列表里,不用挨个注册。
conda activate base conda install -c conda-forge nb_conda_kernels注意要去 base 环境装,然后 Jupyter 启动时它才会自动扫描。缺点是如果环境特别多,内核列表会很长,选起来有点眼花。我个人的选择是:共用服务器上装一个nb_conda_kernels,大家都能顺手选环境;自己日常用的机器还是手动注册,干净可控。
6. 常见问题与排查实录
6.1 问题速查表
下面这些是我实际见过的案例整理成的一份速查表,建议先收藏。
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 浏览器打不开 localhost:8888 | SSH 隧道没建立成功 | 检查 SSH 命令是否报错,用ssh -v看调试输出 |
| 浏览器显示无法连接服务器 | 端口号不一致 | 确认启动 Jupyter 的端口和转发命令里的端口一致 |
| 初次访问弹出 token 框 | 尚未设置固定密码 | 执行jupyter notebook password后重启服务 |
| Notebook 新建后还是 base 环境 | kernel 未注册 | 在对应环境执行ipykernel install |
| 端口被占用 | 有人或之前实例占用 8888 | 用lsof -i:8888查看进程,或换端口启动 |
| Jupyter 启动后马上退出 | 配置项写错 | 看日志,确认ServerApp和NotebookApp配置与版本匹配 |
| 服务器重启后 Jupyter 消失 | 没有使用后台守护方式 | 改用 tmux 或 systemd 方式 |
| 打开 Notebook 一直显示 Connecting | ipykernel 缺失或内核崩溃 | 在对应环境重装 ipykernel,看服务器日志 |
| conda activate 无效 | shell 未初始化 | 执行conda init bash后重新登录 |
6.2 几个真实案例复盘
先说端口占用。又一次我的 Jupyter 突然启动报错,提示端口被占用。排查过程很简单:
lsof -i:8888看到是一个残留的旧进程占着,直接kill掉再启动就好。大多数端口问题的本质就是这个,偶尔也可能是你上次的 nohup 进程还没退出导致的幽灵进程。启动前养成查端口的习惯,能少踩不少坑。
再说内核连接失败。某个师妹反馈,点开 Notebook 之后一直转圈,状态是 Connecting。我看了一下,她的环境里没装 ipykernel,只是在 conda 环境里启动了 jupyter。虽然 Jupyter 进程本身能跑,但新建的就是旧内核,环境里找不到对应的启动脚本,自然就连不上。补装一次 ipykernel 之后就恢复了。这其实就是第 5 节讲的注册问题的变体,很多 New User 都会漏掉。
最后说一个容易忽略的:有些实验室服务器对登录节点有限制,要求只有调度节点能跑计算任务。这时候你把 Jupyter 跑在登录节点上,重负载任务会拖垮整台机器,影响所有同学。这种情况下的正解是申请一段交互式作业,在里面启动 Jupyter,然后同样用 SSH 端口转发访问。具体命令要看实验室的调度系统,但思路是一致的:把 Jupyter 放到有资源的节点上,而不是长期占着登录节点。遇到这类问题,最好先问一下实验室管理员有没有相关约定,别自己埋头配。
6.3 被低估的安全事项补漏
说几个容易被忽略的安全细节:
- 不要用 root 账号跑 Jupyter。Jupyter 一旦出问题,root 权限意味着整台机器被接管。
- 密码尽量用强密码,不要设空密码。有些同学贪方便不设密码,结果服务又开着公网,这等于把服务器大门敞开。
- 日志文件会记录访问来源和 token,不要随意分享给别人。
- 配置里
c.ServerApp.token = ''这种写法强烈不建议,尤其在生产或多人共用环境里。
这些经验不是书上教的,都是我或者同事实际踩过之后总结出来的。有一次某同学图省事,直接开了0.0.0.0监听,结果日志里出现了大量来自陌生地址的访问尝试,吓得我赶紧把服务改成 SSH 转发模式。安全这个东西,一次疏忽可能就是整台服务器的灾难。
7. 我对这套方案的个人补充建议
最后分享一点个人体会。如果你打算在实验室服务器上长期用 Jupyter 工作,我建议一上来就别用老式 Notebook 界面,直接用 JupyterLab。命令相同,配置大同小异,但界面更适合同时开多个 Notebook、终端、文件管理,效率高不少。启动 JupyterLab 只需要把启动命令改成:
jupyter lab --no-browser --port=8888SSH 端口转发和 kernel 配置不用变,直接复用。
另外,如果你经常需要在本地传代码、看结果,可以考虑把 Jupyter 的密码放到~/.jupyter/jupyter_server_config.json里备份一份,或者写一个一键启动脚本。我在服务器上一般放一个start_jupyter.sh:
#!/bin/bash nohup jupyter lab --no-browser --port=8888 > ~/jupyter.log 2>&1 & echo "Jupyter started, check ~/jupyter.log"以后每次连上服务器,只需要bash start_jupyter.sh然后本地跑 SSH 转发,两条命令搞定。这套组合我用了一年多,中间没出过什么大问题。
折腾服务器的过程就是这样——一开始觉得乱,等你把链路各个环节都摸清楚了,之后再遇到问题基本就是"看日志、查端口、对配置"三步走的事。希望这篇记录能帮你少绕点弯路。毕竟环境配置这种事,永远是第一次最痛苦,配好了就一劳永逸。