Caddy 反向代理配合 GeoIP 限制访问区域
我的 Open-WebUI 部署在荷兰,通过同在 Docker 中的 Nginx Proxy Manager 对外暴露。一起使用 Open-WebUI 的朋友们都在中国大陆,直连荷兰速度太慢。
我另外有一台中国大陆优化线路的洛杉矶 VPS,因此在洛杉矶用 Caddy 做反向代理可以缓解连接不畅的问题,顺便通过 MaxMind GeoIP 模块限制只允许中国大陆 IP 访问,提高安全性。计划的架构为:
中国用户 → ai.example.com (洛杉矶 Caddy) → openwebui.example.com (荷兰 NPM)
准备工作
方便起见,先使用 Caddy 官方提供的一键脚本进行安装标准版的 caddy:
apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | tee /etc/apt/sources.list.d/caddy-stable.list
chmod o+r /usr/share/keyrings/caddy-stable-archive-keyring.gpg
chmod o+r /etc/apt/sources.list.d/caddy-stable.list
apt update
apt install caddy
安装带 GeoIP 模块的 Caddy
从 Caddy 官网 下载包含插件的预编译版本(勾选 maxmind_geolocation 插件):
wget -O /root/caddy "https://caddyserver.com/api/download?os=linux&arch=amd64&p=github.com%2Fporech%2Fcaddy-maxmind-geolocation"
chmod 755 /root/caddy
或自己用 xcaddy 构建:
xcaddy build --with github.com/porech/caddy-maxmind-geolocation
用下载或者自行构建的二进制文件替换系统中的 caddy 二进制:
systemctl stop caddy
cp /usr/bin/caddy /usr/bin/caddy.bak
cp ~/caddy /usr/bin/caddy
chmod 755 /usr/bin/caddy
setcap cap_net_bind_service=+ep /usr/bin/caddy
apt-mark hold caddy
验证模块加载:
caddy list-modules | grep -i geo
清理 apt 源(可选)
改用自定义二进制后可以删除 apt 源:
rm /etc/apt/sources.list.d/caddy-stable.list
rm /usr/share/keyrings/caddy-stable-archive-keyring.gpg
apt update
获取 GeoIP 数据库
MaxMind 官方注册有点麻烦,直接从 P3TERX/GeoLite.mmdb 下载现成的:
cd /etc/caddy
wget https://github.com/P3TERX/GeoLite.mmdb/raw/download/GeoLite2-Country.mmdb
配置 Caddyfile
编辑 /etc/caddy/Caddyfile:
{
# Let's Encrypt 证书邮箱
email admin@example.com
}
ai.example.com {
# 定义中国 IP 匹配器
@china {
maxmind_geolocation {
db_path "/etc/caddy/GeoLite2-Country.mmdb"
allow_countries CN # 只允许中国大陆
}
}
# 中国 IP 走反向代理
handle @china {
reverse_proxy https://openwebui.example.com {
# 设置上游 Host 头(必须和上游证书域名一致)
header_up Host openwebui.example.com
# 传递真实客户端 IP
header_up X-Real-IP {remote_host}
transport http {
tls # 上游使用 HTTPS
tls_server_name openwebui.example.com # SNI 名称
keepalive 120s # 长连接保持时间
keepalive_idle_conns 20 # 空闲连接池大小
read_timeout 600s # AI 响应超时(10分钟)
write_timeout 60s
dial_timeout 30s
}
# 禁用缓冲,支持 SSE 流式响应(AI 对话必需)
flush_interval -1
}
# 启用压缩
encode zstd gzip
}
# 非中国 IP 返回 403
handle {
respond "Access Denied" 403
}
# 限制请求体大小
request_body {
max_size 100MB
}
}
关键点在于:
- 不要手动设置
Connection/Upgrade头,Caddy 自动处理 WebSocket,手动设置在 HTTP/2 场景下会报错invalid Upgrade request header tls_server_name必须和上游证书的域名一致,否则会报tls: unrecognized name- AI 对话需要禁用缓冲(
flush_interval -1)支持 SSE 流式响应 - 但是!!!此配置只适用于用户直连 Caddy 的场景。如果 Caddy 前面还有 Cloudflare 等 CDN,
{remote_host}获取到的将是 CDN 节点的 IP 而非用户真实 IP,导致 GeoIP 判断失效(所有请求都会被识别为 CDN 所在地区)。若需配合 CDN 使用,需要配置trusted_proxies并从CF-Connecting-IP等头部获取真实 IP。也有插件可以获取 Cloudflare 的 IP。
验证配置并重启:
caddy validate --config /etc/caddy/Caddyfile
caddy fmt --overwrite /etc/caddy/Caddyfile
systemctl restart caddy
在中国境外测试触发 GeoIP 限制:
curl -I https://ai.example.com
返回信息:
HTTP/2 403
alt-svc: h3=":443"; ma=2592000
content-type: text/plain; charset=utf-8
server: Caddy
content-length: 13
临时测试也可以改成 allow_countries CN US。
自动更新 GeoIP 数据库(可选)
# 创建更新脚本
cat > /usr/local/bin/update-geoip.sh <<'EOF'
#!/bin/bash
# 下载最新的 GeoIP 数据库
cd /etc/caddy
wget -O GeoLite2-Country.mmdb https://github.com/P3TERX/GeoLite.mmdb/raw/download/GeoLite2-Country.mmdb
# 重载 Caddy 配置
systemctl reload caddy
echo "GeoIP updated: $(date)"
EOF
# 设置执行权限
chmod +x /usr/local/bin/update-geoip.sh
# 添加定时任务(每周一凌晨 4 点更新)
(crontab -l 2>/dev/null; echo "0 4 * * 1 /usr/local/bin/update-geoip.sh >> /var/log/geoip-update.log") | crontab -
NPM 中 Open-WebUI 配置
location / {
proxy_pass http://$server:$port;
proxy_http_version 1.1;
# WebSocket 支持
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $http_connection;
# 标准头部设置
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;
# 上传与连接设置
client_max_body_size 100m;
# AI 长响应超时
proxy_read_timeout 600s;
proxy_connect_timeout 60s;
proxy_send_timeout 120s;
send_timeout 120s;
keepalive_timeout 120s;
proxy_redirect off;
# SSE/流式响应优化
proxy_buffering off;
proxy_request_buffering off;
proxy_cache off;
proxy_cache_bypass $http_upgrade;
chunked_transfer_encoding on;
}
结语
实测下来洛杉矶中转效果不错,网页访问和 AI 对话都很流畅。GeoIP 限制也生效了,非中国 IP 直接 403。
其实这些操作问 AI 就行,但是我还是觉得记下来可能更有价值吧。