为什么 2026 年还要自己跑本地大模型
过去两年,几乎每个做 AI 相关工作的团队都经历过同一件事:把一份含有客户名单、合同条款、内部代码的文档贴进在线对话框,然后在某个深夜里突然意识到——这些内容已经离开了自己的电脑。云服务厂商当然有合规承诺,但”承诺”和”数据物理上没离开这台机器”是两件完全不同的事。
Ollama 之所以在开源的本地推理工具里跑得最快,原因很朴素:它把模型下载、量化格式、推理引擎、HTTP 服务四件事打包成了一个你几乎不用配置的黑盒。你敲一行命令,它给你一个监听在 11434 端口的 API;你再配一个 Open WebUI,它给你一个几乎和在线产品一样的聊天界面。对绝大多数非算法岗的工程师、产品经理、法务、医生、教师来说,这就是最低成本的”私有化大模型”入口。
本文只讲一条链路,从装 Ollama 到远程访问,全程可复制执行。中途会穿插大量”别人不会告诉你”的坑:为什么你的 Open WebUI 里模型列表是空的、为什么 11434 端口暴露出去三天后你的 GPU 就在跑别人的任务、为什么 32B 模型在你 16G 显存的机器上”能加载”但一用就崩。这些问题的答案,往往比安装命令本身更值钱。
第一步:安装 Ollama,先搞清楚它到底在跑什么
macOS 与 Linux 的一行安装
Linux 下官方提供了一条 curl 管道脚本,它会下载 Ollama 二进制、创建一个 ollama 系统用户、注册 systemd 服务并开机自启。这是最省事的方式,但你要知道它做了什么,否则后面改监听地址时会找不到入口。
# Linux / WSL2 一行安装(官方脚本)
curl -fsSL https://ollama.com/install.sh | sh
# 安装完成后确认服务状态
systemctl status ollama
# 如果提示服务未运行,手动拉起
sudo systemctl start ollama
sudo systemctl enable ollama
macOS 推荐直接用官网下载的 .dmg 应用包,拖进 Applications 即可。它在菜单栏常驻一个羊驼图标,点击可以看日志、退出、重启。也可以用 Homebrew 安装:
# macOS 用 Homebrew 安装
brew install ollama
# 前台启动服务(适合调试,能看到实时日志)
ollama serve
Windows 安装包与 WSL2 的选择
Windows 用户直接下载 OllamaSetup.exe,双击安装,装完后任务栏右下角会出现 Ollama 图标,服务随之启动。安装程序会把它加到 PATH,所以新开一个 PowerShell 就能用。
这里有一个容易忽略的判断:如果你后面要用 Docker 跑 Open WebUI,Windows 上强烈建议把 Ollama 装在 WSL2 里而不是原生 Windows 里。原因是 Windows 原生 Ollama 监听在 Windows 侧,Docker Desktop 的容器要访问它需要走 host.docker.internal,链路较长;而 WSL2 里的 Ollama 与 Docker 在同一个虚拟网络里,延迟和排查成本都低一个量级。当然,前提是你的显卡驱动和 CUDA 在 WSL2 里跑得通——NVIDIA 卡基本没问题,AMD 卡在 WSL2 里就麻烦得多,这种情况还是老实用原生 Windows 版。
安装后的三条验证命令
装完不要急着拉模型,先用三条命令把地基验一遍。很多人跳过这一步,后面出问题时会误以为是模型或 WebUI 的锅。
# 1. 看版本,确认二进制装上了
ollama --version
# 输出类似:ollama version is 0.9.x
# 2. 看本地已有模型列表(首次为空,正常)
ollama list
# 3. 看服务是否在监听 11434
# Linux / macOS
ss -tlnp | grep 11434
# 或
curl http://127.0.0.1:11434/api/tags
如果第三步返回了 {"models":[]} 这样的 JSON,说明 Ollama 服务已经正常跑起来了,只是还没有模型。这一步是整个链路里最关键的健康检查,后面所有”模型列表为空”的排查,都是从这里开始的。
默认监听 127.0.0.1:11434 意味着什么
Ollama 默认只监听 127.0.0.1,也就是回环地址。这意味着:只有本机能访问它。你的同事在同一个办公室里,用浏览器打开你的 IP 加 11434,是打不开的;同样,Docker 容器里的 Open WebUI 用 http://127.0.0.1:11434 也是打不开的——因为容器里的 127.0.0.1 指的是容器自己。
这个默认值很保守,也很正确。它是整个安全模型的第一道门,几乎所有的远程访问方案,本质上都是在决定”把这道门开到多大”。初学者最常见的错误,是遇到容器连不上就立刻改成 0.0.0.0,结果把门开到公网上去了。正确的做法是只对特定的网络接口、特定的来源开放,这一点在第五节会详细讲。
四个决定性能与体验的环境变量
Ollama 的行为几乎全靠环境变量控制。Linux 下 systemd 服务的变量需要写进 /etc/systemd/system/ollama.service 的 [Service] 段,然后 daemon-reload;macOS 应用版则需要 launchctl setenv 或写入 plist;Windows 在系统环境变量里设置后重启 Ollama 进程。
# Linux:编辑 systemd 服务,加入环境变量
sudo systemctl edit ollama.service
# 在打开的编辑器里粘贴(注意要保留 --- 标记或直接写入 [Service] 段)
[Service]
Environment="OLLAMA_HOST=127.0.0.1:11434"
Environment="OLLAMA_MODELS=/data/ollama/models"
Environment="OLLAMA_KEEP_ALIVE=30m"
Environment="OLLAMA_NUM_PARALLEL=2"
Environment="OLLAMA_MAX_LOADED_MODELS=2"
# 保存后重载并重启
sudo systemctl daemon-reload
sudo systemctl restart ollama
OLLAMA_HOST:改监听地址
格式是 host:port,例如 0.0.0.0:11434 表示监听所有网卡,192.168.1.20:11434 表示只监听那一块网卡。只监听某一块网卡的 IP 是一种被严重低估的做法:如果你的机器同时连着公司内网和咖啡馆 Wi-Fi,只绑定内网网卡,就能在公共网络环境下天然避免暴露。这是比”记得开防火墙”更可靠的物理隔离。
OLLAMA_MODELS:改模型存储路径
默认路径是 macOS 的 ~/.ollama/models 和 Linux 的系统用户目录 /usr/share/ollama/.ollama/models。一个 32B 的 Q4 模型大概 20GB,再加几个不同规格的模型,系统盘很快就满了。把它挪到数据盘是必要操作。注意挪完之后要先把旧模型目录里的内容拷贝过去,否则 ollama list 会变成空的,但磁盘空间还没释放——这是新手最常困惑的场景之一。
# 迁移模型目录的正确步骤
sudo mkdir -p /data/ollama/models
sudo cp -r /usr/share/ollama/.ollama/models/* /data/ollama/models/
sudo chown -R ollama:ollama /data/ollama/models
# 然后再设置 OLLAMA_MODELS 并重启服务
# 确认无误后再手动删除旧目录,不要提前删
OLLAMA_KEEP_ALIVE:模型常驻时长
默认是 5 分钟。超过这个时间没有请求,模型就从显存卸载。对于个人偶尔用一次的场景很合理,但对频繁交互的场景就是灾难:每次提问前都要等 5 到 20 秒把模型重新加载进显存,体感像在用 2010 年的网页。设成 30m 或 -1(永久常驻,注意显存会被一直占用)能显著改善体验。代价是显存不释放,如果你还要跑别的模型或做训练,就别设 -1。
OLLAMA_NUM_PARALLEL:并发数
这个参数决定同一个模型能同时处理几个请求。默认值会根据显存自动推断,通常是 1 或 2。调到 4 会让吞吐量明显提升,但每个请求能用的上下文长度会按比例缩水——因为总显存是固定的,并行度越高,每个槽位分到的 KV Cache 越少。这是 Ollama 里最反直觉的一个权衡:并发数和上下文长度在抢同一块显存。团队共享场景调到 2 到 4 比较务实,个人使用保持 1 反而更好。
第二步:下载并试用 DeepSeek 模型
选哪个规格:7B、14B、32B 还是 70B
DeepSeek-R1 系列在 Ollama 上的蒸馏版本覆盖了多个规格。基础的拉取命令非常统一:
# 按需选择,先拉小的验证链路,再拉大的
ollama pull deepseek-r1:7b
ollama pull deepseek-r1:14b
ollama pull deepseek-r1:32b
ollama pull deepseek-r1:70b
# 也可以用更小的嵌入模型做向量检索
ollama pull nomic-embed-text
需要提醒的是:具体可用的 tag 以 ollama.com/library 页面为准,模型在持续更新,新的量化版本、新的蒸馏规格、甚至模型命名都可能变化。如果你 pull 时报 manifest unknown,第一反应应该是去官网库页面看一眼当前 tag 列表,而不是怀疑网络。
显存估算表:别只看参数量
网上大量文章会告诉你”7B 需要 8G 显存、70B 需要 48G”,这个说法粗糙到会误导人。真实显存占用由三部分构成:权重(参数量 × 每参数字节数)、KV Cache(与上下文长度、层数、隐藏维度成正比)、以及推理时的临时缓冲。下面这张表按每参数字节数给出一个务实区间,假设上下文为 8K,实际会因批次和上下文而浮动。
| 参数量 | 量化等级 | 每参数字节 | 权重显存 | 建议显卡显存 | 实际体感 |
|---|---|---|---|---|---|
| 7B | Q4_K_M | 约 0.55 B | 约 4.5 GB | 8 GB | 笔记本独显可流畅,手机级别不现实 |
| 7B | Q8_0 | 约 1.05 B | 约 8 GB | 12 GB | 接近原模型的推理质量,速度略降 |
| 14B | Q4_K_M | 约 0.55 B | 约 9 GB | 12 GB | 16G 显存的最佳甜点,日常问答够用 |
| 14B | Q8_0 | 约 1.05 B | 约 16 GB | 24 GB | 论文级文本处理明显更稳 |
| 32B | Q4_K_M | 约 0.55 B | 约 20 GB | 24 GB | 单机最强的性价比档,需要 24G 卡 |
| 32B | Q8_0 | 约 1.05 B | 约 35 GB | 48 GB | 单卡基本无缘,需双卡或专业卡 |
| 70B | Q4_K_M | 约 0.55 B | 约 43 GB | 48 GB 起 | Mac Studio 64G 统一内存可跑,速度一般 |
| 70B | Q8_0 | 约 1.05 B | 约 78 GB | 96 GB 起 | 个人硬件基本不现实,接近服务器级 |
表里”建议显存”比”权重显存”高出一截,原因就是 KV Cache。以 32B 模型为例,14G 左右的富余量,在 32K 上下文、并发 4 的情况下会迅速吃满。很多人在 24G 卡上跑 32B Q4 第一个问题很流畅,聊到第二十轮突然卡死或报 OOM,就是这个原因。
Mac 统一内存是另一种算法
Apple Silicon 的显存和内存是共享的,理论上 64G 内存的 Mac Studio 能跑 70B Q4。但实际体验要打折扣:统一内存的带宽远低于独立显卡的 HBM,token 生成速度通常在 8 到 15 token/s 量级,比 RTX 4090 跑 32B 慢不少。它的优势是”能跑”和”安静低功耗”,劣势是”跑得慢”。如果你是想给团队做一个 24 小时在线的服务,Mac 不是好选择;如果是个人离线写作和代码辅助,Mac 反而是性价比最高的方案。
首次运行与进程观察
拉完模型先不要急着配置界面,直接命令行试一下,这是确认”模型本身能不能用”最快的方式。
# 交互式对话
ollama run deepseek-r1:14b
# 进入后直接输入问题,按 Ctrl+D 退出
# 单次非交互提问
ollama run deepseek-r1:7b "用三句话解释什么是向量数据库"
# 查看当前已加载到显存的模型
ollama ps
# 输出会显示 NAME / SIZE / PROCESSOR(100% GPU 表示全部在显存里,
# 出现 CPU 字样说明有部分被换到了内存,速度会掉一个数量级)
# 查看磁盘占用
ollama list # 每个模型的大小
du -sh ~/.ollama/models # 总占用
ollama ps 是最被低估的一条命令。它的 PROCESSOR 列会告诉你模型是否真的全在 GPU 上。当你发现回答速度突然从 30 token/s 掉到 3 token/s,第一件事就是看这里——大概率是显存不够,模型的部分层被卸载到了 CPU,推理被串行化了。
磁盘占用的现实
一个 32B Q4 模型约 20GB,14B Q4 约 9GB,7B Q4 约 4.7GB。看起来还好,但 Ollama 拉取时会有临时的 blob 文件,并且不同 tag 的模型会完整重复存储。如果你试了 7B、14B、32B、70B 各一个,轻松超过 70GB。删模型用 ollama rm deepseek-r1:7b,删完之后记得跑一次 du 确认空间真的释放了。如果发现空间没变,去看看 OLLAMA_MODELS 指向的目录是不是有两个副本。
第三步:用 Docker 装 Open WebUI
一条命令启动,但参数要改
Open WebUI 的官方一条命令长这样,但直接抄会遇到”模型列表为空”的问题,因为 OLLAMA_BASE_URL 的默认值在容器里指向的是容器自身。
# 推荐版本:显式传 Ollama 地址,并挂载数据卷
docker run -d \
-p 3000:8080 \
-v open-webui:/app/backend/data \
--name open-webui \
--restart unless-stopped \
-e OLLAMA_BASE_URL=http://host.docker.internal:11434 \
ghcr.io/open-webui/open-webui:main
注意这里的关键点:OLLAMA_BASE_URL 不能写 127.0.0.1。原因前面说过,容器有自己的网络命名空间,127.0.0.1 是容器自己。在 Docker Desktop(macOS / Windows)里,宿主机有一个特殊的别名 host.docker.internal,容器通过它可以访问宿主机上的服务。这个别名在 Linux 上默认不存在,要么在 run 命令里加 --add-host=host.docker.internal:host-gateway,要么直接改用局域网 IP。
Linux 上的三种正确写法
# 写法 A:host 网络模式,最简单,容器直接用宿主机网络栈
docker run -d --network=host \
-v open-webui:/app/backend/data \
--name open-webui \
-e OLLAMA_BASE_URL=http://127.0.0.1:11434 \
ghcr.io/open-webui/open-webui:main
# 注意:host 模式下端口由环境变量 PORT 控制,默认 8080
# 写法 B:加 host-gateway 映射,保持桥接网络
docker run -d \
-p 3000:8080 \
--add-host=host.docker.internal:host-gateway \
-v open-webui:/app/backend/data \
--name open-webui \
-e OLLAMA_BASE_URL=http://host.docker.internal:11434 \
ghcr.io/open-webui/open-webui:main
# 写法 C:直接用宿主机局域网 IP(需要 Ollama 监听 0.0.0.0)
docker run -d \
-p 3000:8080 \
-v open-webui:/app/backend/data \
--name open-webui \
-e OLLAMA_BASE_URL=http://192.168.1.20:11434 \
ghcr.io/open-webui/open-webui:main
三种写法里,我个人最推荐写法 B。写法 A 最省心但会占用宿主机端口、丢失端口隔离,和其他服务冲突时很难排查;写法 C 要求 Ollama 监听所有网卡,安全面变大。写法 B 兼顾了隔离和连通性,是目前 Linux 上的最优解。
首次访问与管理员注册
浏览器打开 http://localhost:3000,会看到注册页。第一个注册的账号会自动成为管理员,之后 Open WebUI 默认关闭新用户注册(可以在设置里打开)。这一步是安全的,因为如果在你注册之前有人先访问了这台机器,他就能抢走管理员。所以部署完立刻注册,不要留空窗期。
注册完成后,左上角的模型选择器应该能看到 DeepSeek 的模型。如果能看到但对话报错,问题通常在 Ollama 侧的地址或权限;如果根本看不到,问题在连接。
模型列表为空的排查顺序
这是 Open WebUI 领域最高频的问题,几乎每个新部署的人都会遇到。按下面的顺序排查,不要跳步:
- Ollama 是否在跑。在宿主机上执行
curl http://127.0.0.1:11434/api/tags。没返回 JSON,就systemctl status ollama看服务状态。 - Ollama 是否有模型。返回
{"models":[]}说明服务正常但没拉模型,先ollama pull。 - 容器能否访问到 Ollama 地址。进容器里测:
docker exec -it open-webui sh -c "curl http://host.docker.internal:11434/api/tags"。注意容器里可能没有 curl,可以用wget -qO-或python -c。 - Open WebUI 里连接的 URL 是否写对。登录后在设置 → 模型 → 连接 Ollama 里看一眼,改完点保存,必要的时候刷新 Ollama 连接。
- 环境变量是否被旧容器缓存。改完
-e必须docker rm -f open-webui重建容器,不要以为docker restart会生效。
第五点是最容易踩的坑:很多人改完参数执行 docker restart open-webui,然后盯着还是空白的列表发火。环境变量在容器创建时就固化了,restart 不会重新读取 run 命令里传的 -e 参数——你得删掉重建。
进容器测连通的正确命令
# 进容器内部手动测连通性
docker exec -it open-webui python -c "import urllib.request;print(urllib.request.urlopen('http://host.docker.internal:11434/api/tags',timeout=5).read().decode())"
# 如果报 Connection refused:Ollama 没监听或被防火墙拦
# 如果报 Name or service not known:host.docker.internal 别名不存在(Linux 未加 --add-host)
# 如果超时:网络路由问题,检查是否有 VPN 或其他网卡干扰
第四步:API 调用,把 Ollama 变成你自己的后端
原生 API 的四个核心端点
Ollama 自带的 HTTP API 设计得非常直白,全部是 POST 或 GET 的 JSON 接口,没有 OAuth、没有签名、没有限流——这也正是后面安全问题的根源。
| 端点 | 方法 | 用途 | 关键参数 |
|---|---|---|---|
/api/tags |
GET | 列出本地模型 | 无,直接返回 models 数组 |
/api/generate |
POST | 单次补全,不带对话历史 | model、prompt、stream、options |
/api/chat |
POST | 多轮对话,传完整 messages | model、messages、stream、tools |
/api/pull |
POST | 拉取模型,可编程式批量下载 | name、stream |
/api/generate 适合”一问一答”场景,比如批量摘要、格式化转换;/api/chat 才是对接前端聊天的正确选择,因为你需要维护消息历史。两者都支持 stream: true,返回换行分隔的 JSON(NDJSON),前端逐行解析就能实现打字机效果。
OpenAI 兼容端点:零成本迁移
Ollama 在 /v1 路径下实现了一套 OpenAI 兼容接口。这意味着你手上所有基于 openai SDK 写的代码,只要改两个参数就能跑在本地:
from openai import OpenAI
client = OpenAI(
base_url="http://localhost:11434/v1",
api_key="ollama", # 随便填,Ollama 不校验
)
resp = client.chat.completions.create(
model="deepseek-r1:14b",
messages=[{"role": "user", "content": "你好"}],
)
print(resp.choices[0].message.content)
api_key 必须传但不会被校验,这是兼容层的妥协。别指望它能帮你做鉴权,它只是让 SDK 不报错。如果有一天你要把这个端点暴露给外部,请自己在前置网关上加一层 key 校验,不要在本地裸奔。
可运行的 Python 示例
示例一:普通对话
# 普通同步对话,最基础的调用
from openai import OpenAI
client = OpenAI(base_url="http://localhost:11434/v1", api_key="ollama")
def ask(question, model="deepseek-r1:14b"):
resp = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": question}],
temperature=0.6,
)
return resp.choices[0].message.content
if __name__ == "__main__":
print(ask("用一段话解释什么叫 KV Cache,以及它为什么吃显存"))
示例二:流式输出
# 流式输出,适合做终端工具或命令行助手
from openai import OpenAI
client = OpenAI(base_url="http://localhost:11434/v1", api_key="ollama")
def ask_stream(question, model="deepseek-r1:14b"):
stream = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": question}],
stream=True,
temperature=0.6,
)
for chunk in stream:
delta = chunk.choices[0].delta.content
if delta:
print(delta, end="", flush=True)
print()
if __name__ == "__main__":
ask_stream("写一段 Python 代码,把 CSV 按列分组成多个文件")
示例三:带历史的多轮对话
# 多轮对话:自己维护 messages 列表
from openai import OpenAI
client = OpenAI(base_url="http://localhost:11434/v1", api_key="ollama")
class LocalChat:
def __init__(self, model="deepseek-r1:14b", system=None):
self.model = model
self.history = []
if system:
self.history.append({"role": "system", "content": system})
def send(self, text):
self.history.append({"role": "user", "content": text})
resp = client.chat.completions.create(
model=self.model,
messages=self.history,
temperature=0.6,
)
answer = resp.choices[0].message.content
self.history.append({"role": "assistant", "content": answer})
return answer
if __name__ == "__main__":
bot = LocalChat(system="你是一位严谨的数据库架构师,回答尽量简短。")
print(bot.send("我有一张订单表,一千万行,查询慢"))
print("---")
print(bot.send("刚才说的表,加索引有什么讲究?")) # 能接上前文
注意 LocalChat 里的 history 会一直增长,长对话会迅速把上下文撑满。生产代码里要做截断:保留 system 消息 + 最近 N 轮,或者做摘要压缩。这也是本地模型最大的工程细节之一,比在线 API 更需要你自己管上下文预算。
原生 API 的直连写法
如果你不想装 SDK,用 requests 直连也一样干净:
import requests, json
# 原生 /api/chat
r = requests.post(
"http://localhost:11434/api/chat",
json={
"model": "deepseek-r1:7b",
"messages": [{"role": "user", "content": "你好"}],
"stream": False,
},
timeout=120,
)
print(r.json()["message"]["content"])
# 拉模型的编程式写法
r = requests.post(
"http://localhost:11434/api/pull",
json={"name": "deepseek-r1:7b", "stream": False},
timeout=None,
)
print(json.dumps(r.json(), ensure_ascii=False)[:200])
第五步:远程访问,在”能用”和”安全”之间选边
局域网访问的正确姿势
让同一 Wi-Fi 下的手机、平板、同事的电脑访问你的模型,最常见的做法是把 OLLAMA_HOST 改成 0.0.0.0:11434。这确实能成功,但它同时打开了所有网卡。更克制的做法是只绑定局域网网卡的 IP:
# 更安全的写法:只绑内网网卡
OLLAMA_HOST=192.168.1.20:11434
# 然后让 Open WebUI 也监听局域网
docker run -d \
-p 0.0.0.0:3000:8080 \
-v open-webui:/app/backend/data \
--add-host=host.docker.internal:host-gateway \
--name open-webui \
-e OLLAMA_BASE_URL=http://host.docker.internal:11434 \
ghcr.io/open-webui/open-webui:main
Open WebUI 通过 -p 控制监听地址,0.0.0.0:3000:8080 表示所有网卡 3000 端口都监听。如果你只想让内网访问,可以写 192.168.1.20:3000:8080,Docker 会只在这一块网卡上转发。这是很多人不知道的能力:-p 的第一个冒号前可以写 IP。
安全警告:公网暴露 11434 = 把模型送人
这是我必须用最重的语气说的一件事:Ollama 的 11434 端口没有任何身份验证。没有 token、没有 key、没有 IP 白名单。任何人只要知道你的 IP 和端口,就能:
- 用
/api/tags列出你所有模型,知道你在研究什么 - 用
/api/pull给你拉一堆几十 G 的模型,把你的磁盘撑爆 - 用
/api/generate无限次调用,把你的 GPU 占满 - 用
/api/delete删掉你的模型
更讽刺的是,互联网上一直有专门扫描 11434 端口的爬虫,它们会替你完成上面这些操作。我在一些安全审计里见过真实案例:一台被临时映射到公网测试的机器,三天内 GPU 利用率常年 100%,模型列表里多出好几个自己没拉过的模型,账单里多出好几度电。这不是”理论上可能”,这是”一定会”。
Open WebUI 则好一些,它自带账号体系,注册后需要登录。但它的默认配置并不够强:没有强制强密码、没有登录失败锁定、没有 2FA(早期版本)。对于小团队可以接受,对于公网仍然不够。
几种远程访问方式的对比
下面这张表是本文最值得收藏的一张:
| 方案 | 攻击面 | 需要公网 IP | 部署成本 | 适用场景 | 风险等级 |
|---|---|---|---|---|---|
| 直接映射 11434 到公网 | 整个 Ollama API 裸奔 | 是 | 极低 | 无人应在生产使用 | 极高 |
| 映射 Open WebUI 到公网 | 登录页 + 账号体系 | 是 | 低 | 个人临时试用 | 中高 |
| Nginx 反代 + HTTPS 证书 | 加密传输,登录仍可被爆破 | 是 | 中 | 给外部客户交付的小服务 | 中 |
| 防火墙白名单 + 内网 IP 段 | 仅限指定来源 | 否 | 低 | 公司内网、家庭内网 | 低 |
| Tailscale / WireGuard 组网 | 不暴露公网端口,零信任 | 否 | 低 | 异地团队、远程办公 | 很低 |
| SSH 隧道端口转发 | 复用 SSH 认证 | 否 | 极低 | 单人临时远程 | 很低 |
从这张表能读出一个结论:凡是能在不暴露公网端口的前提下实现远程的方案,安全性都自动高一档。Tailscale、WireGuard、SSH 隧道,本质上都是在公网之上套一层加密的虚拟内网,外部扫描器根本看不到你的 11434。
推荐做法:反代加 HTTPS 加来源限制
如果确实需要一个对外的访问入口(比如给客户演示),完整的安全栈应该长这样:
# Nginx 配置示例:只暴露 Open WebUI,不碰 Ollama
server {
listen 443 ssl http2;
server_name ai.example.com;
ssl_certificate /etc/letsencrypt/live/ai.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/ai.example.com/privkey.pem;
# 只允许公司出口 IP 和指定 VPN 段
allow 203.0.113.0/24;
allow 10.8.0.0/24;
deny all;
# 基础限流,防暴力尝试
limit_req zone=one burst=10 nodelay;
client_max_body_size 20m;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_read_timeout 600s; # 流式生成长,超时要放宽
}
}
注意 proxy_read_timeout 600s 这一行。本地模型生成长回答很慢,Nginx 默认 60 秒超时,会出现”前端还在等,连接已经被掐断”的诡异现象。这是自建本地大模型服务最容易被忽略的运维细节之一。
远程团队的场景:Tailscale 是更优解
如果你的团队里有成员在外地、在家里、在客户现场,Tailscale 或 WireGuard 的体验远好于端口映射。Tailscale 的 Tailnet 给每台设备分配一个 100.x 网段的内网 IP,你只需要在 Ollama 所在的机器上装 Tailscale,其他设备加入同一个 Tailnet,就能像访问局域网一样访问。
# 在服务端设备上
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up
tailscale ip -4 # 拿到 100.x.y.z 的地址
# 服务端只监听 Tailscale 这一块网卡
# Linux systemd:
Environment="OLLAMA_HOST=100.x.y.z:11434"
# 团队成员电脑安装 Tailscale 后,直接访问:
# http://100.x.y.z:3000 Open WebUI
# http://100.x.y.z:11434 Ollama API
这种做法的好处是:公网上没有任何端口对外开放,扫描器扫不到;Tailscale 内部是 WireGuard 加密,流量在传输层就保密;设备准入由 Tailnet 管理,丢一台设备直接移除即可。对于”数据不出本机”这个诉求,它在网络层面完全满足——所有流量只在你的设备之间加密传递,不经过第三方的推理服务。
第六步:”数据不出本机”到底有多真
拉模型时的那一次外网下载
“数据不出本机”这句话需要精确化。准确的表述是:推理过程不进云,但模型本体和元数据来自云。拉模型时,Ollama 会从 registry.ollama.ai 下载模型权重,这是一个几百 MB 到几十 GB 的下载,走 HTTPS,内容本身不含你的任何数据,但你的 IP、下载时间、下载的模型名会被服务器记录。这点和 apt install 没有本质区别。
拉完之后,ollama run、/api/generate、Open WebUI 的聊天,全部在本机完成。你的问题、回答、上传的文档,都不会离开这台机器。这点是可以验证的:断开网络,把模型拔到外网都不可达的环境,模型依然能正常回答。
Open WebUI 可能的外连
Open WebUI 在默认配置下有几个可能外连的点,需要在安全评审时明确:
- 更新检查:容器启动时会检查新版本,可以设置
ENABLE_VERSION_UPDATE_CHECK=false关掉。 - 模型列表同步:如果你配置了 ollama.com 作为模型来源,它会去拉公开模型库列表,设置
OFFLINE_MODE=true可全局关闭。 - 社区模型/插件市场:在设置里绑定 ollama.com 账号后,会同步个人模型和工具;不绑定就不会。
- 匿名使用统计:默认开启,可在设置里关闭,也可以用环境变量
ANONYMIZED_TELEMETRY=false关掉。 - 容器镜像拉取:
docker pull本身是外连,这属于基础设施范畴。要完全离线,需要用docker save/docker load传镜像。
# 完全离线的 Open WebUI 启动参数
docker run -d \
-p 127.0.0.1:3000:8080 \
--add-host=host.docker.internal:host-gateway \
-v open-webui:/app/backend/data \
--name open-webui \
-e OLLAMA_BASE_URL=http://host.docker.internal:11434 \
-e OFFLINE_MODE=true \
-e ENABLE_VERSION_UPDATE_CHECK=false \
-e ANONYMIZED_TELEMETRY=false \
ghcr.io/open-webui/open-webui:main
需要诚实地说:OFFLINE_MODE 能关掉大部分功能,但它也会让某些依赖外部服务的功能不可用,比如某些模型的图标、在线文档检索。这是权衡,不是缺陷。
日志与会话记录的本地存储
所有聊天记录、用户、配置都存在 /app/backend/data/webui.db,这是一个 SQLite 数据库,挂在 open-webui 这个 Docker volume 里。它就在你自己的磁盘上,这是”数据不出本机”最实在的部分——但同时也意味着,备份和清理是你的责任。
# 备份数据库:先停容器,再拷贝,避免写一半
docker stop open-webui
docker run --rm -v open-webui:/data -v $(pwd):/backup \
alpine tar czf /backup/openwebui-$(date +%F).tar.gz -C /data .
docker start open-webui
# 查看数据库体积
docker exec open-webui du -sh /app/backend/data/webui.db
# 清理:删掉 30 天前的对话(谨慎操作,先备份)
docker exec -it open-webui python -c "
import sqlite3, datetime
db = sqlite3.connect('/app/backend/data/webui.db')
cutoff = (datetime.datetime.now() - datetime.timedelta(days=30)).isoformat()
cur = db.execute('DELETE FROM chat WHERE updated_at < ?', (cutoff,))
db.commit()
print('deleted rows:', cur.rowcount)
db.close()
"
注意:直接操作 SQLite 前一定要停掉容器或至少确认没有正在写入,否则可能损坏数据库。企业场景更稳妥的做法是重建一个新 volume,把旧数据导出成 JSON 后再导回去。Ollama 自身也会在 ~/.ollama/logs/ 下写日志,其中会记录请求的 prompt 和响应摘要,如果你处理的是敏感数据,这些日志也要定期清理,或者干脆把 OLLAMA_DEBUG 保持关闭。
量化模型和全精度模型的真实落差
本地跑模型的人都会问:Q4 量化的 32B 和原版 32B,效果差多少?诚实的答案是:在绝大多数任务上差距很小,但在长上下文和细节精确度上有可感知的下降。
具体来说:Q8 相比原版基本无损,几乎没有理由不用 Q8(如果显存够);Q4_K_M 在常识问答、摘要、改写、代码生成上表现稳定,但在数学推理的中间步骤、长文档的细节召回、复杂 JSON 格式保持上,错误率会升高。有趣的是,量化对"知识"影响小,对"推理链"影响大——因为推理过程依赖大量的浮点运算稳定性,权重的细微抖动会在多步推理中放大。
所以选模型时,我的建议是:优先用更大的模型跑低量化,而不是小模型跑高量化。14B Q4 往往比 7B Q8 效果好得多,而显存需求几乎一样。这是本地部署里最反直觉的一条经验。
上下文长度受显存限制的现实
模型标签上写的 "128K 上下文",在本地往往跑不到。原因就是 KV Cache:上下文越长,缓存越大。在 24G 显存的卡上跑 32B 模型,实际能稳定用的上下文通常只有 8K 到 16K,虽然可以设 num_ctx 到 32K,但会面临 OOM 或者性能急剧下降。
# 显式设置上下文长度(越小越省显存)
ollama run deepseek-r1:14b
>>> /set parameter num_ctx 8192
# 或者在 API 里传
curl http://localhost:11434/api/chat -d '{
"model": "deepseek-r1:14b",
"messages": [{"role":"user","content":"hi"}],
"options": {"num_ctx": 8192, "num_predict": 2048}
}'
Open WebUI 里也有类似设置,在管理员面板 → 设置 → 模型 → 高级参数里,可以给每个模型设默认上下文。默认值往往偏大,需要根据你的硬件调小。
大多数人做错的地方
错误一:把 11434 端口直接暴露到公网
这是本文最想强调的一条。前面已经详细说过后果,这里补充一个观察:做这件事的人往往不是不懂安全,而是"临时测试一下"。路由器上做个端口映射,本意是下班后在家里试试,结果忘了关。这类事故的共同特征是"临时变成永久"。
正确的习惯是:任何需要临时开放的端口,在开放的那一刻就设一个闹钟,24 小时后必须检查并关闭;或者干脆用 Tailscale 代替,因为它压根不需要开放端口。
错误二:把 API key 设为 "ollama" 却当作安全措施
很多人看到 SDK 里 api_key="ollama" 能跑通,就以为这是某种密钥。它不是。你改成 api_key="mysecret" 也一样能跑,因为服务端压根不读这个字段。真正的鉴权必须在网关层做。
错误三:只看参数量,不看 KV Cache
买卡时盯着"32B 需要 20G",买了 24G 卡,以为万事大吉。实际跑起来发现长对话必崩,或者速度骤降。这是只算了 weight 没算 cache 的典型失败。选卡时至少预留 40% 余量给 KV Cache 和缓冲。
错误四:以为 Open WebUI 的遥测"无所谓"
对于个人使用确实无所谓,但对于医疗、法律、金融这类强合规场景,任何外连都需要在数据流图上画出来。默认的匿名统计哪怕只传版本号和功能使用频次,也要在安全评审里申报。关掉它只需要一个环境变量,但很多人从来没打开过设置页。
错误五:把 Ollama 当成数据库,模型和业务数据混在一个盘
OLLAMA_MODELS 和 Open WebUI 的 webui.db 如果都放在系统盘、跟业务代码的盘混在一起,备份策略会变得非常混乱。建议把模型目录、Open WebUI 数据目录、备份目录三者物理隔离,各自有独立的状态和清理逻辑。
错误六:用 docker restart 代替重建容器
前面提过,但我认为值得单独列出来。Open WebUI 的所有配置几乎都靠环境变量,而环境变量只在容器创建时注入。改配置 = 删容器重建。这条经验能省掉很多人一个下午的排查时间。
错误七:忽视 ollama ps 这个诊断入口
几乎所有"为什么突然变慢"的问题,答案都在 ollama ps 的 PROCESSOR 列。100% GPU 才算全速,出现 CPU 说明有层被卸载,这是性能骤降的直接证据。养成改配置后第一时间看它的习惯。
进阶:让这套方案更稳
Compose 编排,把配置固化下来
用 docker run 一条命令验证可以,但长期使用应该写成 compose 文件,把版本、卷、环境变量、重启策略都固化,避免"重装一次环境又调半天"。
# docker-compose.yml
services:
open-webui:
image: ghcr.io/open-webui/open-webui:main
container_name: open-webui
restart: unless-stopped
ports:
- "127.0.0.1:3000:8080"
extra_hosts:
- "host.docker.internal:host-gateway"
volumes:
- open-webui:/app/backend/data
environment:
- OLLAMA_BASE_URL=http://host.docker.internal:11434
- ENABLE_VERSION_UPDATE_CHECK=false
- ANONYMIZED_TELEMETRY=false
- WEBUI_AUTH=true
volumes:
open-webui:
注意 ports 里写的是 127.0.0.1:3000,这意味着 Open WebUI 只对本机开放,对外的入口必须经过 Nginx 或 Tailscale。这是默认安全的配置,需要远程时再显式打开。
安全检查清单
每次部署完成、每次改动网络配置后,照下面的清单过一遍。这一份可以直接贴进团队的 SOP 文档。
# ===== Ollama 本地部署安全检查清单 =====
[ ] 1. Ollama 未监听 0.0.0.0(除非确实需要,并已加防火墙)
ss -tlnp | grep 11434
# 期望看到 127.0.0.1:11434 或内网 IP,不是 0.0.0.0
[ ] 2. 路由器/云安全组未将 11434 映射到公网
# 登录路由器后台,检查端口转发列表,确认无 11434
[ ] 3. 若必须对外,只暴露 Open WebUI 端口,不暴露 11434
# Open WebUI 有账号体系,Ollama 没有
[ ] 4. Open WebUI 监听地址收紧
docker port open-webui
# 期望 127.0.0.1:3000 或内网 IP
[ ] 5. 已设置强密码并关闭开放注册
# 管理员面板 → 设置 → 用户 → 关闭 "允许新用户注册"
[ ] 6. 遥测与更新检查已关闭
docker inspect open-webui | grep -E "TELEMETRY|OFFLINE"
# 期望看到 "false" / "true"
[ ] 7. Tailscale / WireGuard 组网已启用(远程访问场景)
tailscale status
# 所有客户端都在同一 Tailnet
[ ] 8. 反向代理只允许白名单 IP
nginx -t
# 检查 allow/deny 规则
[ ] 9. 日志清理策略已配置
du -sh ~/.ollama/logs
# 定期清理,或确认轮转已生效
[ ] 10. 数据库备份已跑过一次
ls -lh openwebui-*.tar.gz
# 定期备份,确认可以恢复
常见问题 FAQ
Ollama 装好了,但是拉模型特别慢,有没有加速的方法?
Ollama 的模型仓库没有国内镜像,下载速度取决于你到 registry.ollama.ai 的连接质量。可以尝试:一是设置代理,在启动 Ollama 的进程里设 HTTPS_PROXY 环境变量(注意是 Ollama 服务进程,不是你的 shell);二是在网络状态好的时段拉取,大模型往往一次拉完就长期复用;三是使用 Hugging Face 上同模型的 GGUF 版本手动导入,绕过 Ollama 仓库。优先级上,代理方案最稳,手动导入更适合内网完全隔离的场景。
7B 和 32B 的差距到底有多大,值得为了 32B 换显卡吗?
在常识问答、简单改写这类任务上,7B 已经够用,差距不明显。但在需要多步推理、长文档理解、代码重构的场景里,32B 相比 7B 是质变级别的提升,而与 14B 相比则是明显提升但不夸张。换卡的判断标准不是"参数更大",而是"你当前的工作是否卡在推理深度上"。如果只是写邮件、总结会议记录,24G 卡的投入完全没必要;如果是要替代一部分真正的分析工作,24G 是这条线的门槛。
Open WebUI 里能同时连接多个 Ollama 实例吗?
可以。Open WebUI 的连接设置里支持添加多个 Ollama 地址,每个地址给一个自定义名称,模型列表会按来源合并显示。这个能力适合"一台跑 7B 的轻量机 + 一台跑 32B 的工作站"的组合,让不同任务自动落到合适的机器上。需要注意的时,模型名在不同实例可能重名,建议在命名上做区分,否则选择器里会分不清。
DeepSeek-R1 的思考过程很长,能不能关掉或者缩短?
R1 系列的推理链是模型本身训练出来的行为,Ollama 层面没有"官方开关",但可以通过设置较小的 num_predict 限制总输出长度,避免它一直反思下去。在 Open WebUI 里,思考过程通常会单独折叠显示,不影响阅读。实际使用中,对于简单问题,强制它"直接给答案,不要推理"往往会导致质量下降,因为你在让它违背训练目标;更好的做法是选择非推理型的模型版本用于简单任务,推理型模型留给复杂问题。
本机能跑模型,但同事的浏览器打不开,怎么排查?
按顺序查三件事:第一,在同事电脑上 ping 你的内网 IP,确认网络层通;第二,确认 Open WebUI 的端口映射不是 127.0.0.1:3000:8080,这个写法只对本机开放,要改成 0.0.0.0:3000:8080 或内网 IP;第三,检查宿主机防火墙,Linux 的 ufw 或 firewalld 默认可能拦截 3000 端口。三步都过了还打不开,多半是公司网络做了隔离,需要换用 Tailscale 这类组网方案。
升级 Ollama 或 Open WebUI 会不会把模型和数据弄丢?
Ollama 升级二进制不会删除 ~/.ollama/models 里的模型,Open WebUI 升级镜像也不会删除 volume 里的数据库。但有两个细节要注意:一是升级 Ollama 时如果 systemd 配置被覆盖,OLLAMA_HOST 等环境变量可能丢失,升级后要检查;二是 Open WebUI 跨大版本升级会有数据库迁移,迁移失败可能导致数据损坏,升级前务必先备份 volume。这两条都是踩过坑的经验,不是理论风险。








暂无评论内容