Karakeep 内网访问与中文截图问题排查记录

记录时间:2026-09-02
环境:Ubuntu / Docker Compose / Karakeep / OpenResty / v2rayN
内网服务器:192.168.5.100
内网域名:*.home.52huahua.cn

今天主要折腾了一下 Karakeep。

原本只是想把家里的一些内网页面收藏到 Karakeep,结果一路遇到了几个问题:

  • 浏览器访问 Karakeep 出现 502
  • Karakeep 无法抓取内网域名
  • Crawler 提示禁止访问 192.168.5.100
  • 放行以后又出现 ERR_NAME_NOT_RESOLVED
  • 服务器 DNS 和 IPv6 配置存在问题
  • 最后页面虽然能抓取了,但 Karakeep 生成的截图中文全部变成方框

这几个问题实际上来自不同层级,最后分别定位到了 v2rayN、Karakeep SSRF 防护、服务器 DNS、IPv6 配置和 Chrome 容器字体。


1. 浏览器访问 Karakeep 出现 502

Karakeep 部署在:

192.168.5.100:3000

通过 OpenResty 使用:

karakeep.home.52huahua.cn

进行反向代理。

一开始浏览器访问时直接出现:

502 Bad Gateway

先在服务器端测试 Karakeep:

curl -I http://192.168.5.100:3000

返回:

HTTP/1.1 307 Temporary Redirect
location: /signin
X-Powered-By: Next.js

说明 Karakeep 本身正常。

继续测试 OpenResty:

curl -I http://karakeep.home.52huahua.cn/signin

同样可以正常返回:

HTTP/1.1 200 OK
Server: openresty
X-Powered-By: Next.js

到这里基本可以排除 Karakeep 和 OpenResty 本身的问题。

后来在浏览器开发者工具里发现一个很关键的信息:

Remote Address: 127.0.0.1:10808

这个端口正好是本机 v2rayN 的代理端口。

也就是说浏览器实际上走的是:

浏览器
  ↓
v2rayN / Xray
  ↓
karakeep.home.52huahua.cn

而不是直接访问局域网。

Xray 日志也能看到:

using outbound/direct[direct]

lookup karakeep.home.52huahua.cn:
(exchange4: NXDOMAIN | exchange6: NXDOMAIN)

这里说明 direct 路由规则其实已经匹配成功。

真正的问题是 Xray 自己解析内网域名失败。

所以这个 502 并不是 OpenResty 或 Karakeep 返回的,而是本机代理链路导致的。


2. Karakeep 无法收藏内网页面

浏览器访问解决以后,又发现 Karakeep 无法正常抓取:

https://new.home.52huahua.cn/

先进入 Karakeep 容器测试 DNS:

getent hosts new.home.52huahua.cn

可以正确得到:

192.168.5.100 new.home.52huahua.cn

继续测试:

curl -vI https://new.home.52huahua.cn/

HTTPS、证书、OpenResty 全部正常:

Connected to 192.168.5.100:443
SSL certificate verify ok.
HTTP/2 200
server: openresty

因此可以确认:

DNS        正常
TCP        正常
HTTPS      正常
SSL 证书   正常
OpenResty  正常
目标网站    正常

这时候继续查看 Karakeep Crawler 日志,终于找到了真正原因:

Refusing to access disallowed resolved address 192.168.5.100
for host new.home.52huahua.cn

同时还有:

Disallowed navigation target

Karakeep 默认存在 SSRF 防护,不允许 Crawler 请求解析到私有 IP 的地址。

我的域名:

new.home.52huahua.cn
        ↓
192.168.5.100

正好属于私有网络,因此被 Karakeep 主动拦截。


3. 放行 Karakeep 内网域名

解决方法是在 Karakeep 环境变量中加入:

CRAWLER_ALLOWED_INTERNAL_HOSTNAMES=.home.52huahua.cn

这里使用:

.home.52huahua.cn

表示允许该域名下的内部子域。

例如:

new.home.52huahua.cn
karakeep.home.52huahua.cn
emby.home.52huahua.cn
xxx.home.52huahua.cn

重新创建服务以后检查:

docker exec karakeep-web-1 env | grep CRAWLER_ALLOWED_INTERNAL_HOSTNAMES

返回:

CRAWLER_ALLOWED_INTERNAL_HOSTNAMES=.home.52huahua.cn

说明配置已经正确进入 Karakeep 容器。


4. SSRF 解决以后,又出现 DNS 解析失败

比较有意思的是,加完 CRAWLER_ALLOWED_INTERNAL_HOSTNAMES 以后,错误发生了变化。

原来是:

Refusing to access disallowed resolved address

后来变成:

net::ERR_NAME_NOT_RESOLVED

以及:

getaddrinfo ENOTFOUND new.home.52huahua.cn

这个变化其实是个好现象。

说明 Karakeep 已经不再因为 SSRF 防护拒绝这个域名了,现在真正卡在 DNS。

于是继续检查服务器:

resolvectl dns

发现:

Link 2 (eno1): 114.114.114.114 fe80::5

再查看:

cat /etc/resolv.conf

也是:

nameserver 114.114.114.114
nameserver fe80::5%2
search .

问题就出在这里。

114.114.114.114 是公网 DNS,它并不知道家庭网络里的:

*.home.52huahua.cn

因此内网域名解析自然失败。


5. 发现服务器 DNS 被 Netplan 写死

继续检查:

networkctl status eno1

确认当前网络由:

Netplan
+
systemd-networkd

管理。

查看 Netplan:

cat /etc/netplan/*.yaml

原来的配置类似:

network:
  ethernets:
    eno1:
      addresses:
      - 192.168.5.100/24
      dhcp6: true
      match:
        macaddress: b4:2e:99:63:ce:57
      nameservers:
        addresses:
        - 114.114.114.114
        search: []
      routes:
      - to: default
        via: 192.168.5.1
      set-name: eno1
  version: 2

也就是说,这台服务器不仅 IPv4 地址是静态配置,DNS 同样也是静态写死的。

所以即使路由器 DHCP 下发的 DNS 已经修改,这台服务器也不会跟着变化。


6. 修改服务器 DNS

最终把 Netplan 调整为:

network:
  ethernets:
    eno1:
      addresses:
        - 192.168.5.100/24

      dhcp6: false
      accept-ra: false
      link-local: []

      match:
        macaddress: b4:2e:99:63:ce:57

      nameservers:
        addresses:
          - 192.168.5.100
          - 192.168.5.1
        search: []

      routes:
        - to: default
          via: 192.168.5.1

      set-name: eno1

  version: 2

其中:

192.168.5.100

是服务器本机使用的内部 DNS。

192.168.5.1

作为家庭网络的备用 DNS。

修改完成后重新应用 Netplan 配置。


7. 顺便彻底关闭 IPv6

处理 DNS 的时候还发现:

fe80::5

一直出现在服务器 DNS 配置中。

目前这台服务器并不需要 IPv6,所以顺便彻底关闭。

Netplan 中加入:

dhcp6: false
accept-ra: false
link-local: []

同时创建:

/etc/sysctl.d/99-disable-ipv6.conf

内容:

net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
net.ipv6.conf.eno1.disable_ipv6 = 1

应用:

sysctl --system

检查:

cat /proc/sys/net/ipv6/conf/all/disable_ipv6
cat /proc/sys/net/ipv6/conf/eno1/disable_ipv6

都应该返回:

1

最后检查:

ip -6 addr show dev eno1

已经没有 IPv6 地址。

这里还踩了一个小坑。

仅仅配置:

dhcp6: false

并不代表彻底关闭 IPv6。

之前生成的 systemd-networkd 配置里仍然存在:

LinkLocalAddressing=ipv6

也就是说 IPv6 Link-local 地址仍然会被创建。

因此还需要:

accept-ra: false
link-local: []

才能把这一层也关掉。


8. Karakeep 能抓取了,但截图中文全部变成方框

前面的网络问题全部解决以后,Karakeep 已经可以正常收藏:

https://new.home.52huahua.cn/

结果又遇到了最后一个问题:

Karakeep 自动生成的网页截图中,中文全部显示成方框。

但是:

  • 英文正常
  • 数字正常
  • 图片正常
  • 页面布局正常

所以基本可以排除 UTF-8 或网页编码问题。

Karakeep 的截图实际上走的是:

Karakeep Worker
      ↓
Playwright
      ↓
karakeep-chrome
      ↓
Headless Chromium
      ↓
网页渲染
      ↓
生成截图

所以 Windows 浏览器能正常显示中文没有任何意义。

真正需要中文字体的是:

karakeep-chrome

这个 Docker 容器。


9. 检查 Karakeep Chrome 镜像

当前官方镜像:

ghcr.io/karakeep-app/karakeep-chrome:release

检查系统:

docker exec karakeep-chrome-1 sh -c \
'cat /etc/os-release; echo "---"; command -v apt-get; command -v apk; command -v fc-cache'

得到:

PRETTY_NAME="Debian GNU/Linux 13 (trixie)"
VERSION_ID="13"
VERSION="13 (trixie)"
VERSION_CODENAME=trixie

并且存在:

/usr/bin/apt-get

说明当前 Karakeep Chrome 官方镜像基于 Debian 13。

但是最开始:

fc-list

都无法使用。

说明官方 Chrome 镜像中没有完整安装 fontconfig 和中文字体。


10. 自定义 karakeep-chrome 镜像

决定直接基于官方 Chrome 镜像增加中文字体。

目录结构:

/opt/karakeep/
├── docker-compose.yml
├── .env
└── chrome/
    └── Dockerfile

Dockerfile:

FROM ghcr.io/karakeep-app/karakeep-chrome:release

USER root

RUN printf '%s\n' \
'Types: deb' \
'URIs: http://mirrors.aliyun.com/debian' \
'Suites: trixie trixie-updates' \
'Components: main' \
'Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg' \
'' \
'Types: deb' \
'URIs: http://mirrors.aliyun.com/debian-security' \
'Suites: trixie-security' \
'Components: main' \
'Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg' \
> /etc/apt/sources.list.d/debian.sources

RUN apt-get update \
    && apt-get install -y --no-install-recommends \
       fontconfig \
       fonts-noto-cjk \
       fonts-noto-color-emoji \
    && fc-cache -f \
    && rm -rf /var/lib/apt/lists/*

这里主要安装:

fontconfig
fonts-noto-cjk
fonts-noto-color-emoji

其中真正解决中文缺字问题的是:

fonts-noto-cjk

它提供完整的中日韩字体支持。


11. Debian 国内镜像源又踩了一个坑

最开始为了提高下载速度,使用了清华 Debian 镜像:

https://mirrors.tuna.tsinghua.edu.cn/debian

结果构建时出现:

SSL connection failed:
certificate verify failed

随后又出现:

E: Unable to locate package fontconfig
E: Unable to locate package fonts-noto-cjk
E: Unable to locate package fonts-noto-color-emoji

这里后面的:

Unable to locate package

比较容易误导。

实际上并不是 Debian 13 没有这些包。

真正的问题是前面的:

apt-get update

因为 HTTPS Certificate Verify Failed,没有正常下载软件包索引。

没有 package index 后,apt-get install 自然找不到这些软件包。

最后改成阿里云 HTTP:

http://mirrors.aliyun.com/debian
http://mirrors.aliyun.com/debian-security

成功完成安装。


12. 修改 Docker Compose

原来的 Chrome 服务直接使用官方镜像:

chrome:
  image: ghcr.io/karakeep-app/karakeep-chrome:release

改成:

chrome:
  build:
    context: ./chrome

  command:
    - --disable-gpu
    - --disable-dev-shm-usage
    - --hide-scrollbars
    - --disable-blink-features=AutomationControlled
    - --window-size=1440,900

  init: true
  restart: unless-stopped

检查最终 Compose 配置:

docker compose config | sed -n '/chrome:/,/^[^ ]/p'

能够看到:

chrome:
  build:
    context: /opt/karakeep/chrome
    dockerfile: Dockerfile

说明 Compose 已经正确读取自定义 Build 配置。


13. 最后一个坑:镜像 Build 成功,不代表容器已经使用新镜像

执行:

docker compose build chrome

构建已经成功:

naming to docker.io/library/karakeep-chrome:latest

但是 Karakeep 截图仍然是乱码。

于是检查:

docker exec karakeep-chrome-1 fc-match "sans-serif:lang=zh-cn"

却得到:

exec: "fc-match": executable file not found in $PATH

这个结果明显不对。

因为 Dockerfile 已经安装:

fontconfig

只要运行的是新镜像,fc-match 就应该存在。

于是继续检查当前容器:

docker inspect karakeep-chrome-1 \
  --format 'Image={{.Config.Image}}'

结果:

Image=ghcr.io/karakeep-app/karakeep-chrome:release

再看:

docker compose images

发现运行中的 Chrome 仍然是:

karakeep-chrome-1
ghcr.io/karakeep-app/karakeep-chrome
release
150MB

这时候才发现:

新的 Docker 镜像确实已经 Build 成功,但是正在运行的 Container 根本没有重新创建。

这也是今天最后一个、同时也是最容易忽略的问题。


14. 强制重新创建 Chrome 容器

不需要把整个 Karakeep 全部停掉,只重新创建 Chrome:

docker compose up -d --force-recreate --no-deps chrome

其中:

--force-recreate

强制重新创建 Chrome Container。

而:

--no-deps

避免影响:

karakeep-web
karakeep-meilisearch

重新创建后检查:

docker compose images

终于变成:

karakeep-chrome-1
karakeep-chrome
latest
237MB

之前官方镜像只有:

150MB

现在自定义镜像:

237MB

说明包含 Noto CJK 字体的新镜像终于真正运行起来了。

重新让 Karakeep 抓取页面以后:

中文截图恢复正常。

问题到这里彻底解决。


最终保留配置

Karakeep 内网 Crawler

.env

CRAWLER_ALLOWED_INTERNAL_HOSTNAMES=.home.52huahua.cn

Chrome Docker Compose

chrome:
  build:
    context: ./chrome

  command:
    - --disable-gpu
    - --disable-dev-shm-usage
    - --hide-scrollbars
    - --disable-blink-features=AutomationControlled
    - --window-size=1440,900

  init: true
  restart: unless-stopped

Chrome Dockerfile

FROM ghcr.io/karakeep-app/karakeep-chrome:release

USER root

RUN printf '%s\n' \
'Types: deb' \
'URIs: http://mirrors.aliyun.com/debian' \
'Suites: trixie trixie-updates' \
'Components: main' \
'Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg' \
'' \
'Types: deb' \
'URIs: http://mirrors.aliyun.com/debian-security' \
'Suites: trixie-security' \
'Components: main' \
'Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg' \
> /etc/apt/sources.list.d/debian.sources

RUN apt-get update \
    && apt-get install -y --no-install-recommends \
       fontconfig \
       fonts-noto-cjk \
       fonts-noto-color-emoji \
    && fc-cache -f \
    && rm -rf /var/lib/apt/lists/*

以后更新 Karakeep

由于现在 karakeep-chrome 是基于官方镜像二次构建的,所以以后官方 Chrome 镜像更新后,需要重新 Build。

可以执行:

docker compose pull
docker compose build --pull chrome
docker compose up -d --force-recreate --no-deps chrome

然后确认:

docker compose images

再验证中文字体:

docker exec karakeep-chrome-1 \
  fc-match "sans-serif:lang=zh-cn"

正常应该能够匹配到 Noto CJK 字体。


今天踩到的几个坑

今天这一轮折腾下来,主要记住几个点:

  1. 浏览器能访问内网页面,不代表 Docker Crawler 也能访问,二者 DNS 和网络环境可能完全不同。

  2. Karakeep 默认会阻止 Crawler 访问私有 IP。家庭内网页面需要配置:

    CRAWLER_ALLOWED_INTERNAL_HOSTNAMES=.home.52huahua.cn
    
  3. 错误从:

    Disallowed resolved address
    

    变成:

    ERR_NAME_NOT_RESOLVED
    

    不代表配置没有生效,反而说明已经成功通过了上一层 SSRF 检查。

  4. Netplan 里写死 DNS 后,修改路由器 DHCP DNS 对服务器没有作用。

  5. dhcp6: false 不等于彻底关闭 IPv6,IPv6 Link-local 仍然可能存在。

  6. Headless Chrome 截图中文显示成方框,优先检查 Chrome 容器有没有 CJK 字体。

  7. apt 提示:

    Unable to locate package
    

    不一定真的是包不存在,要往前看 apt-get update 有没有失败。

  8. docker compose build 只负责构建镜像。

    它不代表正在运行的 Container 已经切换到了新镜像。

这个尤其值得记住。

以后修改自定义 Chrome Dockerfile 后:

docker compose build chrome
docker compose up -d --force-recreate --no-deps chrome

然后一定再看:

docker compose images

确认实际运行的镜像。


最终结果

现在整个链路已经正常:

内网 DNS
    ↓
*.home.52huahua.cn
    ↓
192.168.5.100
    ↓
OpenResty
    ↓
Karakeep Crawler
    ↓
CRAWLER_ALLOWED_INTERNAL_HOSTNAMES
    ↓
Headless Chromium
    ↓
Noto CJK
    ↓
网页抓取 + 中文截图正常

今天本来只是想解决一个 Karakeep 收藏内网页面的问题,最后把内网 DNS、IPv6、Karakeep SSRF 和 Headless Chrome 中文字体几个问题一起清掉了。

最大的经验还是:

遇到这种链路型问题,不要只盯着最终的 502、ERR_NAME_NOT_RESOLVED 或“乱码”。顺着 Browser → Proxy → DNS → Reverse Proxy → Application → Crawler → Headless Browser 一层一层验证,问题会清晰很多。