LM Studio Hugging Face 本机劫持教程
用本机 TLS 终止代理,把 LM Studio 的 Hugging Face 下载改写到国内镜像 hf-mirror.com,再直连镜像返回的 CDN。目标是:
- 不走机场 / Clash 节点,不消耗代理流量
- 绕过 Hugging Face 对境外出口 IP 的风控
- 绕过
hf-mirror.com对非国内 IP 的拒绝 - 兼容 LM Studio 的
useHFProxy(实际首跳是search.lmstudio.ai,不是hf-mirror.com)
本教程按 Windows 10/11 + LM Studio 0.4.x 实测整理。核心思路与具体域名、路径改写、证书 SAN 都必须按本文落地,缺一项就会继续连官方 Cloudflare。
推荐直接让agent操作
0. 先搞清楚它为什么慢
LM Studio 是 Electron 应用,模型下载走 Node / undici,不是浏览器。
常见误区:
| 误区 | 实际情况 |
|---|---|
| 开了系统代理,LM Studio 就会走代理 | Electron / Node 下载器不读 Windows 系统代理 |
useHFProxy=true 就会切到 hf-mirror.com |
它只是把 URL 改写成 https://search.lmstudio.ai/v1/hf-proxy/<repo>/... |
改 hosts 劫持 huggingface.co 就够了 |
useHFProxy=true 时首跳是 search.lmstudio.ai |
| Clash 规则里 DIRECT 就等于宽带直连 | Clash 开着时,mihomo 的 DNS / TUN 可能覆盖 hosts |
镜像能直接用 Host: huggingface.co |
后期镜像会按客户端 IP 检测,非国内 IP 会拒绝或跳回官网 |
实测过的真实路径:
text
LM Studio
└─ https://search.lmstudio.ai/v1/hf-proxy/<org>/<repo>/resolve/main/<file>
└─ 本机 127.0.0.1:443 劫持
├─ 去掉前缀 /v1/hf-proxy
├─ Host 改成 hf-mirror.com
└─ 用国内出口直连 160.16.86.14:443
└─ 302
└─ https://us.aws.cdn.hf.co/...
└─ 直连可达的 CloudFront 边缘(本文用 13.214.85.108)
因此必须同时劫持:
search.lmstudio.ai:LM Studio 内置 HF 中转huggingface.co/hf.co/cdn-lfs.huggingface.co:关闭useHFProxy后的直连首跳us.aws.cdn.hf.co:大文件真正落地的 CDN。这一条不要指到127.0.0.1,要指到一个本机能直连的 CDN IP
1. 前置条件
- 宽带本身能直连
hf-mirror.com。hf-mirror会屏蔽非国内IP,先完全退出 Clash / 关闭 TUN 等代理,再访问 hf-mirror.com 。
如果这里就失败,本方案无效。
- 下载期间保持 Clash / TUN 关闭。TUN 的 DNS 劫持会覆盖
hosts。 - 需要管理员权限:安装本地 CA、改
hosts、监听443。 - 需要 Python 3.10+(标准库即可,不用额外包)。
- 需要 OpenSSL(Git for Windows 自带即可)。
- 本机 IPv6 如果通,必须一起劫持。LM Studio 会优先走 Cloudflare IPv6,只改 IPv4 会被绕过。
先确认镜像 IP 和 CDN IP 仍然可达。这两个地址会变,复刻时不要盲抄:
bash
# 查镜像当前 IP
nslookup hf-mirror.com
# 关掉 Clash 后,逐个试 TLS
curl --noproxy "*" -I --resolve hf-mirror.com:443:镜像IP https://hf-mirror.com/
# 查 CDN,再逐个试
nslookup us.aws.cdn.hf.co
本文实测可用的一组:
text
hf-mirror.com -> 160.16.86.14
us.aws.cdn.hf.co -> 13.214.85.108
如果你测到的可达 IP 不同,后面脚本和 hosts 全部改成你自己的。
2. 目录结构
建议统一放到用户目录,后面所有路径按这个改:
text
%USERPROFILE%\hfproxy\
ca.key
ca.crt
server.key
server.crt
server.csr
san.cnf
hf_hijack.py
start_lms_hijack.ps1 # 可选:带 DNS 映射启动 LM Studio
start_proxy_hidden.vbs # 登录自启
PowerShell:
powershell
New-Item -ItemType Directory -Force "$env:USERPROFILE\hfproxy" | Out-Null
cd $env:USERPROFILE\hfproxy
3. 生成本地 CA 和服务器证书
Git Bash 下 openssl -subj /CN=... 会被 MSYS 改成路径,必须先关路径转换。
bash
cd "$USERPROFILE/hfproxy"
export MSYS_NO_PATHCONV=1
# 1. 本地 CA
openssl req -x509 -newkey rsa:2048 -nodes \
-keyout ca.key -out ca.crt -days 3650 \
-subj "/CN=HfHijack Local CA"
# 2. 服务器私钥
openssl genrsa -out server.key 2048
# 3. SAN:必须包含 LM Studio 会访问的所有主机名
cat > san.cnf <<'EOF'
subjectAltName=DNS:huggingface.co,DNS:*.huggingface.co,DNS:cdn-lfs.huggingface.co,DNS:hf.co,DNS:us.aws.cdn.hf.co,DNS:search.lmstudio.ai
EOF
# 4. CSR + 用本地 CA 签发
openssl req -new -key server.key -subj "/CN=huggingface.co" -out server.csr
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
-out server.crt -days 3650 -sha256 -extfile san.cnf
# 5. 核对
openssl verify -CAfile ca.crt server.crt
openssl x509 -in server.crt -noout -ext subjectAltName
search.lmstudio.ai 一定要进 SAN。只签 huggingface.co 时,LM Studio 连本机代理会因主机名不匹配直接失败。
4. 把 CA 装进系统,并让 Node 也信任它
Windows 自带 curl / Schannel 认系统根证书;LM Studio 的 Node 下载器默认不读系统根证书。两边都要处理。
管理员 CMD / PowerShell:
bat
certutil -addstore -f Root "%USERPROFILE%\hfproxy\ca.crt"
当前用户环境变量(不用管理员):
bat
reg add "HKCU\Environment" /v NODE_EXTRA_CA_CERTS /t REG_SZ /d "%USERPROFILE%\hfproxy\ca.crt" /f
立刻对当前会话生效:
powershell
$env:NODE_EXTRA_CA_CERTS = "$env:USERPROFILE\hfproxy\ca.crt"
另外清掉失效的本地代理变量。如果以前给 Clash 写过 HTTP_PROXY=http://127.0.0.1:7897,Clash 关掉后 Node 会连死端口然后超时:
bat
reg delete "HKCU\Environment" /v HTTP_PROXY /f
reg delete "HKCU\Environment" /v HTTPS_PROXY /f
reg delete "HKCU\Environment" /v ALL_PROXY /f
注意:
- 新环境变量只对之后启动的进程生效。改完必须彻底退出并重开 LM Studio。
- Windows curl 是 Schannel 后端,会报
CRYPT_E_NO_REVOCATION_CHECK。这是自签 CA 没有吊销点,不是证书坏了。测试时加--ssl-no-revoke。LM Studio 的 Node / rustls 不做这项检查。
5. 写劫持代理
保存为 %USERPROFILE%\hfproxy\hf_hijack.py。把路径和镜像 IP 改成你自己的。
python
# -*- coding: utf-8 -*-
"""
本机 TLS 终止代理:
1. 伪装成 huggingface.co / search.lmstudio.ai
2. 去掉 /v1/hf-proxy 前缀
3. 把 Host 改成 hf-mirror.com
4. 用国内出口直连镜像 IP
"""
import re
import socket
import socketserver
import ssl
import threading
from pathlib import Path
BASE = Path(__file__).resolve().parent
CERT = str(BASE / "server.crt")
KEY = str(BASE / "server.key")
MIRROR_IP = "160.16.86.14" # 改成你自己测通的 hf-mirror.com IP
MIRROR_HOST = "hf-mirror.com"
LISTEN = ("::", 443) # IPv6 双栈,同时收 127.0.0.1 和 ::1
server_ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
server_ctx.load_cert_chain(CERT, KEY)
server_ctx.set_alpn_protocols(["http/1.1"]) # 强制 HTTP/1.1,方便改 Host
upstream_ctx = ssl.create_default_context()
_HOST_RE = re.compile(rb"^(Host:\s*)([^\r\n]*)", re.MULTILINE)
_REQUEST_RE = re.compile(rb"^([A-Z]+\s+)/v1/hf-proxy/", re.MULTILINE)
def rewrite_request(head: bytes) -> bytes:
# LM Studio: GET /v1/hf-proxy/org/repo/... -> GET /org/repo/...
head = _REQUEST_RE.sub(rb"\1/", head, count=1)
def repl(m):
host = m.group(2).decode("ascii", "ignore").strip()
if host and host != MIRROR_HOST:
return m.group(1) + MIRROR_HOST.encode()
return m.group(0)
return _HOST_RE.sub(repl, head)
def relay(src, dst):
try:
while True:
data = src.recv(65536)
if not data:
break
dst.sendall(data)
except Exception:
pass
finally:
for s in (src, dst):
try:
s.shutdown(socket.SHUT_RDWR)
except Exception:
pass
try:
s.close()
except Exception:
pass
class Handler(socketserver.BaseRequestHandler):
def handle(self):
client = self.request
try:
buf = b""
while b"\r\n\r\n" not in buf:
chunk = client.recv(4096)
if not chunk:
return
buf += chunk
if len(buf) > 100 * 1024:
return
head, rest = buf.split(b"\r\n\r\n", 1)
head = rewrite_request(head)
raw = socket.create_connection((MIRROR_IP, 443), timeout=15)
upstream = upstream_ctx.wrap_socket(raw, server_hostname=MIRROR_HOST)
upstream.sendall(head + b"\r\n\r\n" + rest)
t = threading.Thread(target=relay, args=(client, upstream), daemon=True)
t.start()
relay(upstream, client)
t.join(timeout=2)
except Exception as e:
print(f"[ERR] {e}", flush=True)
class Server(socketserver.ThreadingTCPServer):
address_family = socket.AF_INET6
daemon_threads = True
allow_reuse_address = True
def server_bind(self):
# Windows 默认 V6ONLY=0 也能双栈;显式关掉更稳
self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
super().server_bind()
if __name__ == "__main__":
srv = Server(LISTEN, Handler)
srv.socket = server_ctx.wrap_socket(srv.socket, server_side=True)
print(f"HF hijack proxy listening on {LISTEN[0]}:{LISTEN[1]}", flush=True)
srv.serve_forever()
先手动跑一次,确认没有报错:
bash
python "%USERPROFILE%\hfproxy\hf_hijack.py"
应看到:
text
HF hijack proxy listening on :::443
另开一个终端看监听:
text
0.0.0.0:443 LISTENING
[::]:443 LISTENING
如果 443 被占用,先查是谁:
bat
netstat -ano | findstr ":443"
常见占用:IIS、旧代理实例、某些加速器。本方案必须占本机 443,因为客户端访问的是标准 HTTPS 端口。
6. 改 hosts
管理员记事本打开:
text
C:\Windows\System32\drivers\etc\hosts
先备份,再追加。us.aws.cdn.hf.co 改成你测通的 CDN IP:
text
# === HF hijack ===
127.0.0.1 huggingface.co
127.0.0.1 hf.co
127.0.0.1 cdn-lfs.huggingface.co
127.0.0.1 search.lmstudio.ai
13.214.85.108 us.aws.cdn.hf.co
::1 huggingface.co
::1 hf.co
::1 cdn-lfs.huggingface.co
::1 search.lmstudio.ai
# === end HF hijack ===
然后:
bat
ipconfig /flushdns
用 ping 验证,不要用 nslookup。nslookup 绕过 hosts,会显示真实 DNS。
bat
ping huggingface.co
ping search.lmstudio.ai
ping us.aws.cdn.hf.co
期望:
text
huggingface.co -> 127.0.0.1
search.lmstudio.ai -> 127.0.0.1
us.aws.cdn.hf.co -> 13.214.85.108 (或你自己的可达 IP)
::1 那四条不能省。只写 IPv4 时,LM Studio 会直接连 Cloudflare IPv6。
7. 验证代理本身
Clash 必须处于退出状态。Git Bash / PowerShell 都可以。
bash
# 1. 元数据:应 200
curl --ssl-no-revoke --noproxy "*" -s -m 20 -o /dev/null -w "HTTP %{http_code}\n" \
"https://huggingface.co/gpt2/resolve/main/config.json"
# 2. LM Studio 真实 URL 形态:应 302 后能续传
curl --ssl-no-revoke --noproxy "*" -s -m 60 -L \
-H "Range: bytes=0-8388607" -o /dev/null \
-w "HTTP %{http_code} speed=%{speed_download}\n" \
"https://search.lmstudio.ai/v1/hf-proxy/gpt2/resolve/main/pytorch_model.bin"
正常结果:
- 第一条:
HTTP 200 - 第二条:
HTTP 206,速度大约数 MB/s,而不是 100 KB/s
如果是 HTTP 000 / exit 35:代理没起来,或证书 SAN 不对。 如果是 exit 60:CA 没进系统根证书,或 curl 走了 Schannel 且你没加 --ssl-no-revoke。 如果一直连 Cloudflare IP:hosts 没生效,或 Clash TUN 还开着。
再用 LM Studio 自带 Node 测一次,这才接近真实下载栈:
powershell
$env:NODE_EXTRA_CA_CERTS = "$env:USERPROFILE\hfproxy\ca.crt"
Remove-Item Env:HTTP_PROXY,Env:HTTPS_PROXY,Env:ALL_PROXY -ErrorAction SilentlyContinue
& "$env:USERPROFILE\.lmstudio\.internal\utils\node.exe" -e @"
const https = require('https');
const url = 'https://search.lmstudio.ai/v1/hf-proxy/gpt2/resolve/main/pytorch_model.bin';
const headers = { Range: 'bytes=0-8388607' };
const t0 = Date.now();
function go(u, n=0) {
https.get(u, { headers }, res => {
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location && n < 5) {
console.log('redirect', res.statusCode, new URL(res.headers.location).hostname);
res.resume();
return go(res.headers.location, n+1);
}
let b = 0;
res.on('data', c => b += c.length);
res.on('end', () => {
const s = (Date.now()-t0)/1000;
console.log('HTTP', res.statusCode, 'bytes='+b, 'MB/s='+(b/s/1048576).toFixed(2));
});
}).on('error', e => { console.error(e); process.exit(1); });
}
go(url);
"@
期望输出类似:
text
redirect 302 us.aws.cdn.hf.co
HTTP 206 bytes=8388608 MB/s=3.50
8. 配置 LM Studio
8.1 设置
%USERPROFILE%\.lmstudio\settings.json:
json
"useHFProxy": false
说明:
true:首跳变成search.lmstudio.ai/v1/hf-proxy/...false:首跳是huggingface.co/...
本代理两种都接。更稳妥是关 useHFProxy,同时继续劫持 search.lmstudio.ai,防止应用把旧任务 URL 改回去。
8.2 旧下载任务
已开始的任务会把 URL 写进:
text
%USERPROFILE%\.lmstudio\.internal\download-jobs-info.json
里面可能同时存在:
text
request.url = https://huggingface.co/...
download.url = https://search.lmstudio.ai/v1/hf-proxy/...
LM Studio 恢复任务时可能把 download.url 再写回中转地址。所以不要只改 JSON ,代理必须能处理 /v1/hf-proxy。
改配置前先彻底退出 LM Studio,并备份这两个文件。
8.3 启动方式
普通双击也可以,前提是:
- 代理已在听
443 hosts已生效NODE_EXTRA_CA_CERTS已写入用户环境- 没有失效的
HTTP_PROXY
可选:用 Chromium 主机映射再兜一层。它对 Chromium Network Service 有效,对主进程 Node fetch() 不保证有效,所以不能代替 hosts。
start_lms_hijack.ps1:
powershell
$rules = 'MAP search.lmstudio.ai 127.0.0.1, MAP huggingface.co 127.0.0.1, MAP hf.co 127.0.0.1, MAP cdn-lfs.huggingface.co 127.0.0.1, MAP us.aws.cdn.hf.co 13.214.85.108, EXCLUDE localhost'
Get-Process -Name 'LM Studio' -ErrorAction SilentlyContinue | Stop-Process -Force
Start-Sleep -Seconds 2
$startInfo = New-Object System.Diagnostics.ProcessStartInfo
$startInfo.FileName = 'D:\AI\LLM\LM Studio\LM Studio.exe' # 改成你的安装路径
$startInfo.UseShellExecute = $false
$startInfo.Arguments = '--host-resolver-rules="' + $rules + '" --disable-features=UseDnsHttpsSvcbAlpn,AsyncDns'
$startInfo.EnvironmentVariables['NODE_EXTRA_CA_CERTS'] = "$env:USERPROFILE\hfproxy\ca.crt"
$startInfo.EnvironmentVariables.Remove('HTTP_PROXY')
$startInfo.EnvironmentVariables.Remove('HTTPS_PROXY')
$startInfo.EnvironmentVariables.Remove('ALL_PROXY')
[System.Diagnostics.Process]::Start($startInfo) | Out-Null
9. 登录自启代理
把下面内容存成:
text
%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup\HF Mirror Hijack Proxy.vbs
pythonw.exe 用绝对路径,避免登录 PATH 顺序变化:
vb
Set shell = CreateObject("WScript.Shell")
shell.Run """C:\Path\To\pythonw.exe"" ""%USERPROFILE%\hfproxy\hf_hijack.py""", 0, False
VBS 里 %USERPROFILE% 不会自动展开,写成真实绝对路径,例如:
vb
Set shell = CreateObject("WScript.Shell")
shell.Run """G:\application\scoop\apps\python313\current\pythonw.exe"" ""C:\Users\你的用户名\hfproxy\hf_hijack.py""", 0, False
重启后用 netstat -ano | findstr ":443" 确认还在监听。
10. 怎样判断已经走通
下载开始后看三件事。
1. 连接目标
PowerShell:
powershell
Get-NetTCPConnection -State Established |
Where-Object { (Get-Process -Id $_.OwningProcess -EA SilentlyContinue).ProcessName -eq 'LM Studio' } |
Select-Object OwningProcess, LocalAddress, RemoteAddress, RemotePort
成功时应该看到:
127.0.0.1:443或[::1]:443(首跳进了本机代理)- 以及 / 或者
13.214.85.108:443(大文件直连 CDN)
失败时会看到 Cloudflare:
text
172.67.x.x
104.26.x.x
2606:4700:...
2. 速度
走通后通常是数 MB/s 到十几 MB/s。还是 100 KB/s 左右,基本还在打官方 Cloudflare。
3. 日志
text
%APPDATA%\LM Studio\logs\main.log
证书失败会立刻报错。如果已经下到百分之几再 Timed-out,多半是中转链路慢或本地代理环境变量指向了死端口,不是 CA 问题。
11. 卸载 / 回滚
- 退出 LM Studio。
- 结束
python/pythonw里的hf_hijack.py。 - 删掉
hosts里# === HF hijack ===整段。 ipconfig /flushdns。- 删除启动文件夹里的
HF Mirror Hijack Proxy.vbs。 - 可选:从"受信任的根证书颁发机构"删除
HfHijack Local CA。 - 可选:删除用户环境变量
NODE_EXTRA_CA_CERTS。 - 把
useHFProxy改回你原来的值。
hosts 备份一般在改之前手动复制一份即可。
12. 复刻时最容易踩的坑
-
只劫持了
huggingface.co。LM Studio 开着
useHFProxy时,真实首跳是search.lmstudio.ai。 -
只改了 IPv4
hosts。LM Studio 会走
2606:4700:...的 Cloudflare IPv6。 -
Clash / TUN 还开着。
TUN DNS 会盖掉
hosts,看起来"映射无效"。 -
把
us.aws.cdn.hf.co也指到127.0.0.1。CDN 证书对不上,而且大文件没必要再进本地代理。应指到一个本机能直连的 CDN IP。
-
证书 SAN 漏了
search.lmstudio.ai。握手阶段就会被 Node 拒掉。
-
只把 CA 装进了 Windows 根证书库。
LM Studio 的 Node 还要
NODE_EXTRA_CA_CERTS,并且必须重启应用。 -
残留
HTTP_PROXY=http://127.0.0.1:7897。Clash 关了之后,Node 会连死端口,日志里全是
Timed-out。 -
镜像 IP / CDN IP 过期。
160.16.86.14和13.214.85.108只是当时测通的值。复刻当天要重测。 -
在境外网络上用这个方案。
hf-mirror.com会检测客户端 IP,非国内出口会被拒绝。本方案依赖"本机宽带是国内 IP"。 -
用
nslookup验证hosts。它不走 hosts。用
ping或 PowerShell[System.Net.Dns]::GetHostAddresses('huggingface.co')。
13. 最小检查清单
按顺序勾:
- Clash / TUN 已完全退出
- 宽带能直连
hf-mirror.com - 已确认当前可用的镜像 IP、CDN IP
- 已生成 CA + 带完整 SAN 的服务器证书
- CA 已进系统根证书
- 已设置
NODE_EXTRA_CA_CERTS - 已删除失效的
HTTP_PROXY/HTTPS_PROXY/ALL_PROXY -
hf_hijack.py正在监听0.0.0.0:443和[::]:443 -
hosts含 IPv4 + IPv6,且search.lmstudio.ai指向本机 -
us.aws.cdn.hf.co指向可达 CDN IP,而不是127.0.0.1 - curl / Node 探测已拿到
200/206和数 MB/s - 已彻底重启 LM Studio
- 下载进程连的是
127.0.0.1或 CDN IP,不是2606:4700/172.67
全部勾完后,再在 LM Studio 里点下载。