Miao's Blog

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
    }
}

关键点在于:

验证配置并重启:

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 就行,但是我还是觉得记下来可能更有价值吧。