Kutt 3.2.6 常见问题与排错指南
Kutt 3.2.6 排错 FAQ 全集:启动缺变量退出、JWT 只认 Cookie、注册依赖 SMTP、API key 轮换即失效、字段名与校验边界、重复 slug 返 500、统计 isbot 过滤与 60 秒缓存、Redis/Postgres 宕机挂起与自愈、健康检查盲区,全部为实测复现与处置。
Kutt 3.2.6 常见问题与排错指南
目标:汇集 Kutt v3.2.6 部署与运行中的高频故障,每条均为实测栈(Ubuntu 24.04 +
kutt/kutt:v3.2.6+ Postgres 16 + Redis 7,端口 8153)上真实复现的现象、定位过程与处置方法(2026-11-01)。环境搭建见 《Kutt 3.2.6 Docker Compose 部署指南》,日常运维见 《Kutt 3.2.6 日常运维与升级指南》。
一、环境信息
- 操作系统:Ubuntu 24.04.5 LTS(x86_64),Docker 29.9.2 / Compose v5.6.1
- 被测栈:
kutt/kutt:v3.2.6(Node.js 22.23.1 基座)+postgres:16-alpine+redis:7-alpine - 应用端口:宿主机 8153 → 容器 3000;
DEFAULT_DOMAIN=localhost:8153 - 管理员:
admin@kutt.local/q1w2e3r4;API key 走x-api-key头 - 复现约定:下文
curl均针对http://localhost:8153,<APIKEY>为POST /api/v2/auth/apikey生成的密钥,<UUID>为链接响应中的id字段
二、启动与配置类
Q1:容器反复重启,日志报 Missing environment variables: JWT_SECRET,怎么办?
实测现象:
================================
Missing environment variables:
JWT_SECRET: undefined
================================
Exiting with error code 1
定位与处置:镜像默认 NODE_ENV=production,而 JWT_SECRET 在生产模式下是 envalid 必填项(其 devDefault 仅在开发模式兜底)。在 .env 写入 JWT_SECRET=$(openssl rand -hex 32) 后 docker compose up -d 重建即可。同类问题还包括 compose 变量引用了 .env 中不存在的键(如 ${DB_PASSWORD} 未定义时传空串,Postgres 认证失败)。
Q2:应用日志刷 Error: getaddrinfo EAI_AGAIN postgres 或 Connection ended unexpectedly?
实测:docker compose stop postgres 后应用日志立刻出现:
Connection Error: Connection ended unexpectedly
Error: getaddrinfo EAI_AGAIN postgres
这是数据库容器不可达(宕机或 DNS 未就绪),不是应用故障。docker compose start postgres 后应用自动重连自愈,无需重启 server(实测恢复后 health 返回 OK、跳转恢复 302)。若是首次启动就报此错,检查 compose 的 depends_on.postgres.condition: service_healthy 是否被删——没有健康门槛时应用可能早于库就绪启动,报一次后也会自行重连。
Q3:REDIS_ENABLED=true 时 Redis 挂了,短链跳转直接超时?
实测复现:停掉 Redis 后:
curl -s -m 8 -o /dev/null -w 'HTTP %{http_code} time %{time_total}s\n' http://localhost:8153/6ZErgk
HTTP 000 time 8.000919s
跳转与建链请求全部挂起直至客户端超时(ioredis 重试阻塞请求处理),日志伴随 [ioredis] Unhandled error event: Error: getaddrinfo EAI_AGAIN redis。结论:开启 Redis 后它是跳转链路强依赖,不是"缓存可选件"。处置:优先恢复 Redis;应急可把 REDIS_ENABLED 置 "false" 并重建 server(跳转退回纯 Postgres 路径,代价是失去访问队列与 stats 缓存)。Redis 恢复后同样自动自愈(实测 3 秒内跳转恢复 302)。
Q4:健康检查返回 OK,但短链根本跳不动——探针为什么没报警?
实测:docker compose stop postgres 期间执行:
curl -s http://localhost:8153/api/v2/health
OK
/api/v2/health 是纯静态响应(源码只有一行 res.send("OK")),不探测数据库与 Redis,存在"进程活着但全功能不可用"的盲区。生产监控必须叠加端到端探测:对一条已知短链发 curl -sI,断言 HTTP/1.1 302 与 Location 正确,才能覆盖依赖故障。
三、认证与账号类
Q5:Authorization: Bearer <JWT> 调 API 返回 401 Unauthorized.?
Kutt 3.x 的 JWT 策略只从 Cookie token 提取(源码 passport.js:jwtFromRequest: req => req.cookies?.token),Authorization 头无效。正确姿势:
curl -s -X POST http://localhost:8153/api/v2/auth/apikey -b "token=<JWT>"
{"apikey":"7sGm14IdbxoknkpnItuKQVRTMBaucEQscN7kAXsJ"}
脚本集成推荐拿到 API key 后统一走 x-api-key: <APIKEY> 头,免去会话维护。
Q6:注册新用户总是 401,注册页提交无反应?
实测:MAIL_ENABLED 保持默认 false 时 POST /api/v2/auth/signup 返回:
{"error":"Unauthorized."}
源码路由 auth.featureAccess([!env.DISALLOW_REGISTRATION, env.MAIL_ENABLED]) 要求同时放开注册开关且启用邮件——注册必须经 SMTP 验证邮箱。单机自用不需要注册:首启向导建的账号即 ADMIN;开放注册则先配全 MAIL_HOST/MAIL_PORT/MAIL_USER/MAIL_PASSWORD 并置 MAIL_ENABLED=true、DISALLOW_REGISTRATION=false。
Q7:/create-admin 页面打不开或报 Can not create the admin user because a user already exists.?
首启建管仅在全库零用户时开放(源码先查 findAny)。管理员密码遗忘走数据库重置(见 Q8);误建了普通账号想重建管理,可清库重来:
docker exec kutt-3.2.6-postgres psql -U postgres -d kutt -c "TRUNCATE users, links, visits, domains, hosts, ips RESTART IDENTITY;"
⚠️ 注意:TRUNCATE 清空全部业务数据,仅限测试环境;执行后首页重新 302 到
/create-admin。
Q8:管理员忘记密码且没配 SMTP,如何找回?
找回密码端点依赖邮件。无邮件时直接改库:先在应用容器内用自带 bcryptjs 生成哈希,再更新 users 表:
docker exec kutt-3.2.6-server node -e "require('bcryptjs').hash('q1w2e3r4', 12).then(h=>console.log(h))"
$2a$12$L6bH3nUy…(bcrypt 哈希,实测 60 字符)
docker exec kutt-3.2.6-postgres psql -U postgres -d kutt -c \
"UPDATE users SET password='<上一步哈希>' WHERE email='admin@kutt.local';"
随后用新密码登录验证:
curl -s -X POST http://localhost:8153/api/v2/auth/login \
-H 'Content-Type: application/json' \
-d '{"email":"admin@kutt.local","password":"q1w2e3r4"}'
{"token":"eyJhbGciOiJIUzI1NiIs…(JWT)"}
Q9:重新生成 API key 后,旧 key 立刻 401?
实测:同一账号再次 POST /api/v2/auth/apikey 返回新 key(HTTP 201),旧 key 立即失效:
{"apikey":"fbG9Y_6zirV1z5zY0FTEvneUe5nDC6pDaIxngExb"}
用旧 key 请求:
{"error":"Unauthorized."}
每账号仅保留最新一把 key。轮换流程:先更新所有集成方配置,再生成新 key;或反之接受短暂 401。Redis 中 u:<apikey> 键即按 key 缓存的用户映射,解释了旧 key 的即时失效。
四、创建短链类
Q10:匿名调 POST /api/v2/links 返回 401?
默认 DISALLOW_ANONYMOUS_LINKS=true(实测单容器最小形态未放开时匿名建链返回 {"error":"Unauthorized."})。带 x-api-key 认证,或确需开放匿名短链再置 false——公开实例慎用,会被滥用刷链。
Q11:报 Target is missing. 或 URL is not valid.?
字段名是 target(不是 targetUrl/url),且校验严格。实测各类拒绝响应:
{"error":"Target is missing."} # 缺字段/空串,HTTP 400
{"error":"URL is not valid."} # "not a url" 或指向 DEFAULT_DOMAIN 自身(防自环),HTTP 400
{"error":"Maximum URL length is 2040."} # 超长 URL,HTTP 400
带协议缺失的 example.com 会被自动补 https://(addProtocol 清洗器),无需手工加前缀。
Q12:自定义 slug 冲突为什么返回 HTTP 500 而不是 409?
实测:customurl 撞已用地址时:
{"error":"Custom URL is already in use."} HTTP 500
Kutt 把该冲突当服务端异常抛出(状态码 500),集成脚本不能只按 4xx 判失败——对 500 响应需解析 error 文案区分真故障与业务冲突。字符集不合法(含空格/斜杠等)则正常返回 400:
{"error":"Custom URL is not valid."}
默认 LINK_CUSTOM_ALPHABET 剔除易混字符(o、O、0、i、I、l、1、j),且 login、admin、settings 等保留字不可占用。
Q13:密码/有效期字段被拒?
实测边界回显:
{"error":"Password length must be between 3 and 64."} # password "ab",HTTP 400
{"error":"Expire time should be more than 1 minute."} # expire_in "30s",HTTP 400
expire_in 接受 ms 库时长字符串(1m、12h、30d、1w),下限 1 分钟;实测 "30d" 存库转绝对时间 2026-12-01T16:36:36.880Z。password 与 customurl 同为注册用户专属(匿名请求带这两个字段报 Only users can use this field.)。
Q14:DELETE /api/v2/links/<address> 报 ID is invalid.?
删除、编辑、统计、ban 的路径参数一律要求链接 UUID(响应体的 id 字段),不接受 6 位 address。实测:
{"error":"ID is invalid."} HTTP 400 # DELETE /api/v2/links/QP2zEg
{"message":"Link has been deleted successfully."} HTTP 200 # DELETE /api/v2/links/cd96a9d2-e289-4e74-8fa9-b1a6944cc91b
先 GET /api/v2/links 拿 id → address 映射再操作。
五、跳转与统计类
Q15:短链跳转是 302,能改成 301 吗?
实测响应:
HTTP/1.1 302 Found
Location: https://www.quzuo.net
302 为应用硬编码行为(跳转处理器统一 res.redirect(302)),无配置项可改。若业务确需 301 永久重定向(SEO 权重迁移),只能在应用前置 Nginx 用 return 301 改写,但会失去 Kutt 的访问统计(统计依赖应用内跳转钩子)。
Q16:访问了很多次,visit_count 和 stats 都是 0?
实测三连定位:
- isbot 过滤:
curl默认 UA 的 5 次访问计数为 0;换浏览器 UA 后 3 次访问计数 3:
curl -s -A 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0 Safari/537.36' \
-e 'https://google.com' -o /dev/null -w '%{http_code}\n' http://localhost:8153/6ZErgk
302
- 异步队列:访问事件经 Redis Bull 队列(
bull:visit:*)落visits表,秒级延迟; - stats 缓存 60 秒:首次查询若聚合未就绪,空结果会被缓存 60 秒(Redis 键
s:<链接ID>,TTL 60),等一分钟后复查即出数(实测缓存过期后total: 3、referrer: google[dot]com)。
监控/压测流量默认不计统计;确需计数爬虫访问无官方开关。
Q17:stats 里国家全是 unknown、referrer 显示 google[dot]com?
实测回显:
country: [{'name': 'unknown', 'value': 3}]
referrer: [{'name': 'google[dot]com', 'value': 3}]
国家来自镜像内置 geoip-lite 离线库,内网/测试出口 IP 无匹配即 unknown(属正常,公网部署可命中);referrer 的 [dot] 替换是刻意的防注入混淆展示,非数据损坏。
Q18:被封禁/不存在的短链分别什么表现?
实测:
GET /55RnhZ(已 ban) → HTTP/1.1 302 Found, Location: /banned
GET /nonexist1(不存在) → HTTP/1.1 302 Found, Location: /404
两者都是 302 到站内提示页而非 410/404 状态码——外部工具判断链接状态要看 Location 而非状态码。ban 操作仅 ADMIN:POST /api/v2/links/admin/ban/<UUID>,实测返回 {"message":"Banned link successfully."}。
六、运维与升级类
Q19:升级后想确认迁移是否执行/是否需要手工跑迁移?
镜像 CMD 自带 npm run migrate(knex migrate:latest),每次启动自动迁移且幂等。实测 v3.2.5 ↔ v3.2.6 切换日志:
Already up to date # 两版本迁移集合相同
Batch 1 run: 10 migrations # 空库首启全量建表
核对迁移记录:
docker exec kutt-3.2.6-postgres psql -U postgres -d kutt -t -c \
"SELECT name FROM knex_migrations ORDER BY id DESC LIMIT 3;"
20250106070444_remove_cooldown.js
20241223155527_visits_user_id_index.js
20241223103044_visits_user_id.js
Q20:能回滚到旧版本吗?
3.2.x 补丁间回滚实测安全(迁移集合相同,改 tag 重建即可)。跨小版本回滚因 knex 不回退已执行迁移,必须先 pg_dump 恢复升级前备份再换旧镜像(流程见运维篇 3.2/6.2)。2.x(MongoDB)与 3.x(关系库)数据模型不同代,不存在直升路径,迁移需导出重建。
Q21:改了 DEFAULT_DOMAIN 为什么 API 返回的 link 字段没变?
DEFAULT_DOMAIN 决定返回体 link 前缀(实测 localhost:8153 配置下返回 https://localhost:8153/6ZErgk),改后需重建 server 容器生效;已存库的短链地址(address)不受影响,仅展示前缀变化。协议前缀由 CUSTOM_DOMAIN_USE_HTTPS 控制——实测该值为默认 false 时返回串仍显示 https://,以反代实际 TLS 配置为准,勿依赖此字段判断跳转协议。
Q22:TRUST_PROXY 该怎么设?
默认 true:从 X-Forwarded-For 取访客 IP。直连部署(无清洗反代)时客户端可伪造该头污染统计与限流判定,官方样例明确建议直连设 false。置于 Nginx 之后则保持 true 并确保 Nginx 覆盖(而非透传)X-Forwarded-For。
七、参考链接
- Kutt 仓库与 Issues:https://github.com/thedevs-network/kutt
- 官方环境变量样例:https://github.com/thedevs-network/kutt/blob/main/.example.env
- 部署实操:《Kutt 3.2.6 Docker Compose 部署指南》
- 运维与升级:《Kutt 3.2.6 日常运维与升级指南》
- 选型背景:《Kutt 3.2.6 介绍与选型指南》
- 同类组件排错参考:《Shlink 5.1.8 常见问题与进阶实战》