Anubis 1.28 Docker Compose 部署实战
Docker Compose 编排部署 Anubis v1.28.2 反 AI 爬虫质询代理的完整实战:官方镜像选型、策略文件挂载、直连后端与经 Anubis 质询的全链路对照实测、浏览器模拟 PoW 解题换 Cookie、metrics 与日志核验,以及 X-Real-IP 缺失与 HTTP 循环质询两个排障实录。
Anubis 1.28 Docker Compose 部署实战
目标:在一台 Linux 服务器上以 Docker Compose 编排部署 Anubis v1.28.2 反 AI 爬虫质询代理,并用一个静态站点作为被保护上游,完整演示「直连后端 vs 经 Anubis 质询」的防护链路:curl 模拟浏览器请求被下发 Proof-of-Work 质询、脚本完成 PoW 计算拿到通行 Cookie 后直达上游、Amazonbot 被 DENY 拒绝、metrics 指标与日志核验,以及两次真实排障实录(缺失 X-Real-IP 返回 500、HTTP 站点默认 Secure Cookie 导致循环质询)。全部命令与回显均已在 Ubuntu 24.04 服务器实测通过。选型依据见 《Anubis 介绍与选型指南》。
一、环境信息
- 操作系统:Ubuntu 24.04.5 LTS(x86_64)
- Docker Engine:29.9.2,Docker Compose:v5.6.1
- Anubis:
ghcr.io/techarohq/anubis:v1.28.2(官方 OCI 镜像,2026-10-28 查询的官方最新稳定版) - 被保护上游:
nginx:alpine提供的静态演示站 - 端口规划(宿主侧):
8133→ Anubis 质询入口(容器内 8923)、8135→ 上游静态站直连对照(容器内 80)、8136→ Prometheus 指标端点(容器内 9090) - 独立网络:
anubis-net,显式子网10.89.62.0/24 - 工作目录:
/opt/anubis/(compose 编排、策略文件、演示站点目录均存放于此) - 质询难度:
DIFFICULTY=4(PoW 结果需满足 SHA-256 哈希前导 4 个零)
二、镜像选型说明
Anubis 官方仅发布 OCI 镜像于 ghcr.io/techarohq/anubis,tag 约定如下(官方 installation 文档原文约定,查询日期 2026-10-28):
| Tag | 含义 | 选型建议 |
|---|---|---|
latest | 最新稳定发布 | 不确定时从这里开始 |
pre | 预发布版 | 想提前体验即将转正的功能时使用 |
v<版本号> | 指定版本发布(如 v1.28.2) | 生产推荐,版本锁定可复现 |
main | main 分支当前构建 | 仅用于跟进未发版的新特性 |
本篇实测固定使用 v1.28.2:生产部署应锁定具体版本号而非 latest,保证策略行为与质询算法随版本可追溯。镜像以 UID 1000/GID 1000 运行(官方约定),若挂载外部卷需保证该属主可写——本篇策略文件以只读方式挂载,不受影响。
拉取镜像(输出实测,2026-10-28):
docker pull ghcr.io/techarohq/anubis:v1.28.2
3d4c4b73c020: Pull complete
Digest: sha256:5fcbce70ee6168be5b226d19bec90111d0acd0daf13028fa06b044cf3ebbbd5f
Status: Downloaded newer image for ghcr.io/techarohq/anubis:v1.28.2
验证本地镜像:
docker images | grep anubis
ghcr.io/techarohq/anubis v1.28.2 5fcbce70ee61 71.2MB 18.3MB
单实例镜像体积约 71MB,官方文档注明 Anubis 运行期常驻内存通常低于 32MB,属极轻量的过滤层。
三、前置条件
- 服务器已安装 Docker Engine 29.x 与 Compose v2 插件(本篇实测版本见环境信息);
- 端口 8133、8135、8136 未被占用(用
ss -ltn | grep -E '8133|8135|8136'核查,无输出即可用); - 明确部署拓扑:Anubis 是过滤型反向代理,必须放在「TLS 终结层(Nginx/Caddy 等)」与「被保护业务」之间,一个 Anubis 实例保护一个上游服务。本篇为演示完整链路,用 Compose 同时编排被保护的静态站,生产中该上游通常是你已有的 GitLab/Gitea/论坛等独立服务;
- 已想好策略基线:本篇采用「最小演示策略」(放行
/.well-known/、favicon、robots.txt,DENY 掉 Amazonbot,对含 Mozilla/Opera 的 UA 一律下发质询)。
四、部署步骤
4.1 创建工作目录与演示站点
mkdir -p /opt/anubis/www
cd /opt/anubis
创建上游静态演示页 /opt/anubis/www/index.html:
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="utf-8"><title>Anubis 保护的后端演示站</title></head>
<body>
<h1>Hello from upstream backend</h1>
<p>This page is served by the upstream static site behind Anubis.</p>
</body>
</html>
4.2 编写质询策略文件
创建 /opt/anubis/botPolicy.yaml(官方 policies 文档的最小策略样例基础上,补充一条 AI 爬虫 DENY 规则):
bots:
- name: deny-amazonbot
user_agent_regex: Amazonbot
action: DENY
- name: well-known
path_regex: ^/.well-known/.*$
action: ALLOW
- name: favicon
path_regex: ^/favicon.ico$
action: ALLOW
- name: robots-txt
path_regex: ^/robots.txt$
action: ALLOW
- name: generic-browser
user_agent_regex: Mozilla|Opera
action: CHALLENGE
规则自上而下依次匹配,命中即裁决:deny-amazonbot 把 UA 含 Amazonbot 的请求直接拒绝;well-known/favicon/robots-txt 三条放行「维持互联网运转」的基础路径;generic-browser 对所有像浏览器的 UA(含 Mozilla/Opera)下发质询。没有任何规则命中时请求默认放行——所以纯 curl/8.5.0 这样的 UA 不含 Mozilla,会直通上游(下文 4.5 实测可验证)。策略体系的完整参数见 《Anubis v1.28.2 配置参数详解》。
4.3 编写 Compose 编排
创建 /opt/anubis/compose.yaml(项目名显式声明为 anubis-1.28.2-stack,避免与其他栈混淆):
name: anubis-1.28.2-stack
services:
anubis:
image: ghcr.io/techarohq/anubis:v1.28.2
container_name: anubis-1.28.2-server
environment:
BIND: ":8923"
DIFFICULTY: "4"
METRICS_BIND: ":9090"
SERVE_ROBOTS_TXT: "true"
TARGET: "http://anubis-1.28.2-backend"
POLICY_FNAME: "/data/cfg/botPolicy.yaml"
SLOG_LEVEL: "DEBUG"
COOKIE_SECURE: "false"
COOKIE_SAME_SITE: "Lax"
WEBMASTER_EMAIL: "root@example.com"
ports:
- "8133:8923"
- "8136:9090"
volumes:
- ./botPolicy.yaml:/data/cfg/botPolicy.yaml:ro
healthcheck:
test: ["CMD", "anubis", "--healthcheck"]
interval: 5s
timeout: 30s
retries: 5
start_period: 500ms
networks:
anubis-net:
ipv4_address: 10.89.62.10
depends_on:
- backend
backend:
image: nginx:alpine
container_name: anubis-1.28.2-backend
volumes:
- ./www:/usr/share/nginx/html:ro
ports:
- "8135:80"
networks:
anubis-net:
ipv4_address: 10.89.62.20
networks:
anubis-net:
driver: bridge
ipam:
config:
- subnet: 10.89.62.0/24
编排要点逐项说明:
TARGET指向上游服务的网络内地址(http://anubis-1.28.2-backend,Compose 网络内按服务可达的容器名解析);生产中替换为你的业务服务地址,也支持unix:///path/to/sock;COOKIE_SECURE: "false"与COOKIE_SAME_SITE: "Lax":这两项是纯 HTTP 演示链路能走通的关键(官方默认COOKIE_SECURE=true、COOKIE_SAME_SITE=None,均要求 HTTPS 上下文;HTTP 下保留默认值会陷入循环质询,排障实录见 6.2);SLOG_LEVEL: "DEBUG":观测期开 DEBUG 便于在日志中看到每条请求的规则裁决与质询流转,稳定后建议回调INFO;healthcheck用官方提供的anubis --healthcheck子命令做进程级健康自检;- 策略文件以
:ro只读挂载进/data/cfg/botPolicy.yaml,与POLICY_FNAME对应。
4.4 启动栈并验证健康状态
cd /opt/anubis && docker compose up -d
Container anubis-1.28.2-backend Starting
Container anubis-1.28.2-backend Started
Container anubis-1.28.2-server Starting
Container anubis-1.28.2-server Started
数秒后确认两服务均为 healthy/Up:
docker compose ps --format '{{.Name}}\t{{.Status}}'
anubis-1.28.2-backend Up 8 seconds
anubis-1.28.2-server Up 8 seconds (healthy)
再确认启动日志无 ERROR(输出节选,实测首尾):
docker compose logs anubis 2>&1 | tail -2
{"time":"2026-10-27T17:30:04.763293895Z","level":"DEBUG","source":{"function":"github.com/TecharoHQ/anubis/internal.RemoteXRealIP","file":"github.com/TecharoHQ/anubis/internal/headers.go","line":70},"msg":"skipping middleware, useRemoteAddress is empty"}
{"time":"2026-10-27T17:30:04.76317453Z","level":"INFO","source":{"function":"main.run","file":"github.com/TecharoHQ/anubis/cmd/anubis/main.go","line":487},"msg":"listening","url":"http://localhost:8923","difficulty":4,"serveRobotsTXT":true,"target":"http://anubis-1.28.2-backend","version":"v1.28.2","use-remote-address":false,"custom-real-ip-header":"","debug-benchmark-js":false,"og-passthrough":false,"og-expiry-time":86400000000000,"base-prefix":"","cookie-expiration-time":604800000000000,"rule-error-ids":{"deny-amazonbot":"d7f8451854c8a486"},"public-url":""}
listening 日志里 version/difficulty/target 与编排一致,启动即成功。
4.5 完整链路实测:直连后端 vs 经 Anubis 质询
对照一:直连上游(8135),不经过 Anubis,任何 UA 直接拿到真实页面:
curl -sS -o /dev/null -w "HTTP %{http_code}\n" http://127.0.0.1:8135/
HTTP 200
curl -s http://127.0.0.1:8135/ | head -3
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="utf-8"><title>Anubis 保护的后端演示站</title></head>
对照二:经 Anubis(8133)+ 非浏览器 UA,未命中质询规则,默认放行直达上游(需带 X-Real-IP,缺失该头的报错见排障实录 6.1):
curl -sS -o /dev/null -w "HTTP %{http_code} (direct pass-through)\n" \
-H "X-Real-IP: 203.0.113.10" http://127.0.0.1:8133/
curl -s -H "X-Real-IP: 203.0.113.10" http://127.0.0.1:8133/ | grep -o "<h1>.*</h1>"
HTTP 200 (direct pass-through)
<h1>Hello from upstream backend</h1>
对照三:经 Anubis + 浏览器 UA,命中 generic-browser 规则被下发 PoW 质询。注意状态码是 200 而非 403——这是官方刻意设计(默认 status_codes: {CHALLENGE: 200, DENY: 200},让最激进的爬虫拿到 200 后停手):
curl -sS -o /dev/null -w "HTTP %{http_code}\n" \
-A "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0" \
-H "X-Real-IP: 203.0.113.10" http://127.0.0.1:8133/
curl -s -A "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0" \
-H "X-Real-IP: 203.0.113.10" http://127.0.0.1:8133/ \
| grep -oE "<title>[^<]*</title>"
HTTP 200
<title>Making sure you're not a bot!</title>
对照四:AI 爬虫 UA(Amazonbot),命中 deny-amazonbot 被拒绝,返回让爬虫误以为成功的错误页:
curl -sS -o /dev/null -w "HTTP %{http_code}\n" \
-A "Amazonbot/1.0 (+https://developer.amazon.com/amazonbot)" \
-H "X-Real-IP: 203.0.113.10" http://127.0.0.1:8133/
curl -s -A "Amazonbot/1.0" -H "X-Real-IP: 203.0.113.10" http://127.0.0.1:8133/ \
| grep -oE "<title>[^<]*</title>" | head -1
HTTP 200
<title>Oh noes!</title>
四组对照即为防护链路的行为基线:真实浏览器 UA 被拦在质询页,AI 爬虫被静默拒绝,非浏览器 CLI 流量默认直通。
4.6 模拟浏览器完成 PoW:从质询页到通行 Cookie
真实浏览器会在质询页内执行 JavaScript 计算 PoW 后自动回传。为验证全链路,用 Python 脚本复演浏览器行为:解析质询页内嵌的 challenge JSON → 暴力计算满足难度 4 前导零的 nonce → GET 跳转 pass-challenge 接口换取 Cookie → 重访上游。脚本保存为 /opt/anubis/pow_client.py:
import hashlib, json, re, time, urllib.parse, urllib.request
BASE = "http://127.0.0.1:8133"
UA = "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0"
jar = {}
def req(path):
r = urllib.request.Request(BASE + path)
r.add_header("User-Agent", UA)
r.add_header("X-Real-IP", "203.0.113.10")
if jar:
r.add_header("Cookie", "; ".join(f"{k}={v}" for k, v in jar.items()))
class NoRedirect(urllib.request.HTTPRedirectHandler):
def redirect_request(self, *a, **k): return None
try:
resp = urllib.request.build_opener(NoRedirect).open(r, timeout=30)
except urllib.error.HTTPError as e:
resp = e
for h in resp.headers.get_all("Set-Cookie") or []:
kv = h.split(";")[0]
k, _, v = kv.partition("=")
jar[k.strip()] = v.strip()
return resp
# 第 1 步:请求受保护页面,拿到服务端渲染的质询页与内嵌 challenge JSON
resp = req("/")
html = resp.read().decode()
ch = json.loads(re.search(r'id="anubis_challenge" type="application/json">(.*?)</script>', html, re.S).group(1))
print("step1 challenge page ->", resp.status, "| challenge id:", ch["challenge"]["id"])
print(" method:", ch["challenge"]["method"], "| difficulty:", ch["challenge"]["difficulty"])
# 第 2 步:本地暴力计算(等价于浏览器内 sha256 worker 的循环)
random_data = ch["challenge"]["randomData"]
difficulty = ch["challenge"]["difficulty"]
start = time.time()
nonce = 0
while True:
digest = hashlib.sha256((random_data + str(nonce)).encode()).hexdigest()
if digest.startswith("0" * difficulty):
break
nonce += 1
cost = int((time.time() - start) * 1000)
print(f"step2 PoW solved: nonce={nonce} sha256={digest} elapsed={cost}ms")
# 第 3 步:GET 跳转 pass-challenge 回传 proof,换取通行 Cookie
qs = urllib.parse.urlencode({"id": ch["challenge"]["id"], "response": digest,
"nonce": str(nonce), "redir": "/", "elapsedTime": str(cost)})
resp = req("/.within.website/x/cmd/anubis/api/pass-challenge?" + qs)
print("step3 pass-challenge ->", resp.status, "| location:", resp.headers.get("Location"))
print(" cookies now:", list(jar.keys()))
# 第 4 步:持凭证重访受保护页面
resp = req("/")
body = resp.read().decode()
print("step4 revisit / ->", resp.status)
t = re.search(r"<h1>(.*?)</h1>", body)
print(" upstream title:", t.group(1) if t else body[:100])
执行与实测回显(2026-10-28):
python3 /opt/anubis/pow_client.py
step1 challenge page -> 200 | challenge id: 01a17e6b-e7bb-76fe-a2cb-95876e86f7de
method: fast | difficulty: 4
step2 PoW solved: nonce=19767 sha256=00001302174bcfd0dd65a46bedbeaf8adc996a9667825e33a63dce0de554f1bb elapsed=9ms
step3 pass-challenge -> 302 | location: /
cookies now: ['techaro.lol-anubis-cookie-verification-a9d36a45', 'techaro.lol-anubis-auth-a9d36a45', 'techaro.lol-anubis-original-referer-a9d36a45']
step4 revisit / -> 200
upstream title: Hello from upstream backend
全链路证据链解读:
- 质询页 200 返回,内嵌
anubis_challengeJSON:method: fast、difficulty: 4、randomData为质询随机串; - 客户端对
randomData + nonce逐次 SHA-256,第 19767 次得到前 4 位为零的哈希(00001302…),耗时 9ms——真实浏览器中这由质询页内置的 sha256 worker(WASM/多线程)完成,难度 4 对普通设备是一瞬间的开销; - 携带
id/nonce/response/redirGET 跳转pass-challenge,Anubis 校验通过后 302 回redir,同时签发 3 枚 Cookie(…-anubis-auth-…为通行凭证,后缀a9d36a45由DYNAMIC_COOKIE_SUFFIX按当前 Cookie 配置自动生成,防止同名 Cookie 死循环); - 持凭证重访直接 200 拿到上游真实页面,且凭证默认 168 小时有效(
COOKIE_EXPIRATION_TIME)。
4.7 metrics 与日志核验
访问指标端点,确认质询行为的量化痕迹(输出节选,实测):
curl -s http://127.0.0.1:8136/metrics | grep -E "^anubis_(challenges|policy_results)"
anubis_challenges_issued{asn="",asn_description="",method="embedded"} 3
anubis_challenges_validated{asn="",asn_description="",method="fast"} 1
anubis_policy_results{action="ALLOW",asn="",asn_description="",rule="default/allow"} 2
anubis_policy_results{action="CHALLENGE",asn="",asn_description="",rule="bot/generic-browser"} 4
anubis_policy_results{action="DENY",asn="",asn_description="",rule="bot/deny-amazonbot"} 2
指标与 4.5 的行为一一对应:embedded 计数是下发的质询页数,validated 是 PoW 通过数(4.6 完成的那次),policy_results 按「动作/规则」维度分桶计数。
再从日志侧核验质询通过记录(SLOG_LEVEL=DEBUG 下的关键行,实测):
docker compose logs anubis 2>&1 | grep "challenge accepted" | tail -1
{"time":"2026-10-27T17:32:43.344108865Z","level":"INFO","source":{"function":"github.com/TecharoHQ/anubis/lib.(*Server).PassChallenge","file":"github.com/TecharoHQ/anubis/lib/anubis.go","line":754},"msg":"challenge accepted","subsystem":"anubis","host":"127.0.0.1:8133","method":"GET","path":"/.within.website/x/cmd/anubis/api/pass-challenge","user_agent":"Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0","accept_language":"","priority":"","x-forwarded-for":"","x-real-ip":"203.0.113.10","check_result":{"name":"bot/generic-browser","rule":"CHALLENGE","weight":0},"challenge":"01a17e6b-e7bb-76fe-a2cb-95876e86f7de"}
日志中的 check_result 字段直接给出命中的规则名(bot/generic-browser)、动作(CHALLENGE)与权重(0),是排查「某条请求为什么被拦/放行」的第一入口。
五、数据持久化说明
Anubis 自身不承载业务数据,需要持久化的只有两类:
- 策略文件:
/opt/anubis/botPolicy.yaml,本篇以只读 bind mount 挂载,改动文件后docker compose restart anubis生效; - 质询临时状态:默认
memory存储后端,重启后已签发的通行 Cookie 全部失效(用户需重新过质询)。若在意重启体验或未来横向扩容,改用持久存储——在策略文件中追加store段(如bbolt单机落盘或valkey远端共享),详见 《Anubis v1.28.2 配置参数详解》;使用 bbolt 等持久后端时必须固定ED25519_PRIVATE_KEY_HEX(否则每次启动随机生成密钥,历史 Cookie 全部作废),启动日志中的随机密钥告警即为提示:
{"level":"WARN","msg":"generating random key, Anubis will have strange behavior when multiple instances are behind the same load balancer target, for more information: see https://anubis.techaro.lol/docs/admin/installation#key-generation"}
上游静态站数据在 /opt/anubis/www/,同样随 bind mount 持久化。验证持久化路径:docker compose down && docker compose up -d 后,/opt/anubis/ 下三份文件均原地保留,重挂即恢复。
六、环境变量表
本篇编排用到的环境变量实测清单(默认值核对自官方 installation 文档,查询日期 2026-10-28):
| 环境变量 | 本篇取值 | 默认值 | 作用 |
|---|---|---|---|
BIND | :8923 | :8923 | Anubis 主监听地址(质询入口) |
TARGET | http://anubis-1.28.2-backend | http://localhost:3923 | 质询通过后转发的上游服务地址 |
POLICY_FNAME | /data/cfg/botPolicy.yaml | 未设(用内置默认策略) | 策略文件路径 |
DIFFICULTY | 4 | 4 | PoW 难度(前导零个数) |
METRICS_BIND | :9090 | :9090 | Prometheus 指标服务监听地址 |
SERVE_ROBOTS_TXT | true | false | 由 Anubis 直接下发「拒绝全部 AI 爬虫」的 robots.txt |
COOKIE_SECURE | false | true | Cookie 是否带 Secure 标记;纯 HTTP 部署必须显式关掉,否则循环质询 |
COOKIE_SAME_SITE | Lax | None | Cookie SameSite 属性;None 要求 HTTPS,HTTP 用 Lax |
SLOG_LEVEL | DEBUG | INFO | 结构化日志级别(DEBUG/INFO/WARN/ERROR) |
WEBMASTER_EMAIL | root@example.com | 未设 | 错误页展示的站长联系方式,便于被误伤用户申诉 |
更完整的变量(COOKIE_DOMAIN、COOKIE_EXPIRATION_TIME、ED25519_PRIVATE_KEY_HEX、BASE_PREFIX、TARGET_HOST 等三十余项)逐项解析见 《Anubis v1.28.2 配置参数详解》。
七、配置参数说明
围绕本篇编排出现的关键配置,给出选择依据:
DIFFICULTY: "4":官方默认值。每加 1,PoW 计算量约乘 16(前导零概率 2^-4 → 2^-5);真实用户浏览器 4≈几十毫秒、6 已有明显感知、16 视为「不可能完成」的惩罚档。对普通展示站保持 4 即可,对确认的爬虫群可单独在规则里用challenge.difficulty加码;SERVE_ROBOTS_TXT: "true":实测curl http://127.0.0.1:8133/robots.txt输出以逐个点名的User-agent:段开头的长列表(AddSearchBot、AI2Bot、Amazonbot、AIWebIndex 等),随后 Disallow 全站。上游没能力改 robots.txt 时用它兜底,与策略文件里的robots-txtALLOW 规则配合(Anubis 自己应答,不转发上游);COOKIE_SECURE/COOKIE_SAME_SITE组合:HTTPS 生产站用默认true/None(跨站 iframe 嵌入场景才需要COOKIE_PARTITIONED=true的 CHIPS 分区);本篇 HTTP 演示链路必须false/Lax,排障实录 6.2 展示了不这么做的后果;SLOG_LEVEL:DEBUG 级别会输出每条请求的规则裁决细节(challenge requested、policy result等),观测期强烈建议开启;长期运行用 INFO,只保留质询通过与拒绝的 INFO 行。
八、配置文件挂载
本篇 compose 共两处只读挂载,路径约定如下:
宿主路径 容器路径 用途
/opt/anubis/botPolicy.yaml → /data/cfg/botPolicy.yaml(:ro) 质询策略(POLICY_FNAME 指向)
/opt/anubis/www/ → /usr/share/nginx/html(:ro) 上游静态站点(演示用)
挂载校验方法:改动 botPolicy.yaml 后重启并确认策略被重新加载:
cd /opt/anubis && docker compose restart anubis
docker compose logs anubis 2>&1 | grep "loading policy file"
{"time":"2026-10-27T17:30:04.728135337Z","level":"INFO","source":{"function":"main.run","file":"github.com/TecharoHQ/anubis/cmd/anubis/main.go","line":308},"msg":"loading policy file","fname":"/data/cfg/botPolicy.yaml"}
fname 显示容器内路径且无解析报错,说明挂载与语法均正确;若 YAML 语法错误,Anubis 启动即失败并在日志给出具体行号。
九、等价启动方式(docker run)
与上述 compose 等价的最小 docker run 形态(上游地址换成可达的 TARGET 即可),便于在无 Compose 的环境手动拉起:
docker run -d \
--name anubis-1.28.2-server \
-p 8133:8923 \
-p 8136:9090 \
-v /opt/anubis/botPolicy.yaml:/data/cfg/botPolicy.yaml:ro \
-e BIND=":8923" \
-e TARGET="http://anubis-1.28.2-backend" \
-e POLICY_FNAME="/data/cfg/botPolicy.yaml" \
-e DIFFICULTY="4" \
-e METRICS_BIND=":9090" \
-e SERVE_ROBOTS_TXT="true" \
-e COOKIE_SECURE="false" \
-e COOKIE_SAME_SITE="Lax" \
--network anubis-net \
ghcr.io/techarohq/anubis:v1.28.2
生产推荐 Compose 而非裸 docker run:变量、挂载与健康检查声明式落盘,随 compose.yaml 版本化管理。
十、常用运维命令
cd /opt/anubis
docker compose ps # 两服务应为 Up(anubis 应带 healthy)
docker compose logs -f anubis # 跟踪质询裁决日志(CTRL+C 退出)
docker compose restart anubis # 改策略文件后重启生效
docker compose down # 停止并移除本栈(保留 /opt/anubis 文件)
docker compose up -d # 依编排重新拉起
docker compose exec anubis anubis --healthcheck # 手动触发健康自检
anubis --healthcheck 实测回显(退出码 0):
2026/10/27 17:33:54 running healthcheck
十一、常见问题(含排障实录)
11.1 排障实录一:所有请求返回 500「Oh noes!」,日志报 X-Real-IP header is not set
现象:栈启动 healthy,但 curl 访问 8133 一律 500:
curl -sS -o /dev/null -w "HTTP %{http_code}\n" http://127.0.0.1:8133/
HTTP 500
页面为 Anubis 的 Oh noes! 错误页。查日志定位:
docker compose logs anubis 2>&1 | grep '"level":"ERROR"' | tail -1
{"time":"2026-10-27T17:30:24.225662956Z","level":"ERROR","source":{"function":"github.com/TecharoHQ/anubis/lib.(*Server).maybeReverseProxy","file":"github.com/TecharoHQ/anubis/lib/anubis.go","line":339},"msg":"check failed","subsystem":"anubis","host":"127.0.0.1:8133","method":"GET","path":"/","user_agent":"curl/8.5.0","accept_language":"","priority":"","x-forwarded-for":"","x-real-ip":"","err":"[misconfiguration] X-Real-IP header is not set"}
定位:err 字段写得很直白——[misconfiguration] X-Real-IP header is not set。Anubis 的部署形态是「前置反代 → Anubis」,它默认信任并要求前置层通过 X-Real-IP 告知客户端真实 IP(用于规则裁决、GeoIP 与日志),直连测试时没有任何反代传这个头,就触发此错误。
解决(按部署形态三选一):
- 前置 Nginx 加头(官方 nginx 接入样例的标准写法):
location / {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_pass http://anubis;
}
- Caddy:
reverse_proxy http://anubis:8923 {
header_up X-Real-IP {remote_host}
}
- 测试/裸跑场景让 Anubis 直接读 TCP 对端地址:编排里加
USE_REMOTE_ADDRESS: "true"。
本篇实测采用模拟反代加头的方式验证:
curl -sS -o /dev/null -w "HTTP %{http_code} (direct pass-through)\n" \
-H "X-Real-IP: 203.0.113.10" http://127.0.0.1:8133/
HTTP 200 (direct pass-through)
11.2 排障实录二:PoW 计算通过却永远拿不到上游页面(HTTP 站点循环质询)
现象:保持官方默认 COOKIE_SECURE=true(未加 COOKIE_SECURE: "false")跑 HTTP 演示链路,客户端严格按流程解出 PoW、pass-challenge 也正常 302,但重访页面时又被下发新的质询页,无限循环。
复现(模拟真实浏览器:明文 HTTP 上不回传 Secure Cookie):
first visit -> 200 | challenge page: True
PoW solved nonce=165763 in 79ms
pass-challenge -> 302
set-cookie: techaro.lol-anubis-cookie-verification-eab25f5d secure=True
set-cookie: techaro.lol-anubis-auth-eab25f5d secure=True
revisit / -> 200 | challenge again: True | upstream ok: False
定位:Anubis 默认签发的 Cookie 带 Secure 标记(要求仅在 HTTPS 传输),而演示链路是明文 HTTP——真实浏览器不会在非安全上下文保存/回传 Secure Cookie,于是每次重访都是「无凭证新会话」,质询白算。这是把「HTTPS 假设」套在 HTTP 部署上的典型错配。
解决:HTTP 环境显式改两项编排变量并重建:
COOKIE_SECURE: "false"
COOKIE_SAME_SITE: "Lax"
cd /opt/anubis && docker compose up -d anubis
重跑 4.6 的 PoW 脚本,step4 直接命中上游页面(upstream title: Hello from upstream backend)即修复。HTTPS 生产站务必改回默认值,不要为省事全局关闭 Secure。
11.3 禁用 JavaScript 的客户端会怎样
质询页自带无 JS 降级提示,实测抓取:
<noscript><p>Sadly, you must enable JavaScript to get past this challenge. This is required because AI companies have changed the social contract around how website hosting works. A no-JS solution is a work-in-progress.</p></noscript>
即默认 PoW 质询下禁 JS 客户端无法通过(RSS 阅读器、纯文本浏览器会被拦)。官方提供 metarefresh 无 JS 质询算法(页面定时刷新换凭证,无需执行 JS),代价是对现代无头爬虫的拦截力显著下降;对开放注册的小站点可评估启用,写法见配置详解篇。
11.4 状态码都是 200,怎么监控「被拦了多少」
见 4.7:不要按 HTTP 状态码监控(CHALLENGE/DENY 默认都回 200),改抓 8136 的 anubis_policy_results/anubis_challenges_* 指标,或按日志的 check_result 字段统计。
十二、参考链接
- 官方仓库:https://github.com/TecharoHQ/anubis (查询日期 2026-10-28)
- 官方安装文档(含 Compose 样例):https://anubis.techaro.lol/docs/admin/installation (查询日期 2026-10-28)
- 官方 Docker Compose 环境文档:https://anubis.techaro.lol/docs/admin/environments/docker-compose (查询日期 2026-10-28)
- 官方镜像:https://github.com/TecharoHQ/anubis/pkgs/container/anubis (查询日期 2026-10-28)
- 本专栏选型篇:《Anubis 介绍与选型指南》
- 本专栏命令与运维篇:《Anubis 1.28 常用命令与运维实战(核心篇)》