BoxBilling 连接 DirectAdmin 时提示服务器没有 IP
结论
这个错误通常不表示服务器网卡没有 IP,而是 BoxBilling 通过 DirectAdmin API 查询时,没有获取到当前登录账号可用于创建账户的 IP。
建议先检查两项:
- DirectAdmin 中是否已有可用 IP,并将其设置为
Shared,或分配给 BoxBilling 使用的 reseller。 - BoxBilling 使用的 DirectAdmin 账号是否有权查看该 IP。
如果 DirectAdmin 后台能看到可用 IP,但 BoxBilling 仍然报错,可能是旧版 extension 无法解析新版 DirectAdmin API 的响应格式。此时需要更新或修补 Directadmin.php。
先检查 DirectAdmin 的 IP 配置
以管理员身份进入 DirectAdmin 的 IP 管理页面,逐项确认:
- 服务器 IP 已添加到 DirectAdmin。
- IP 未处于异常或停用状态。
- 创建普通 user 时,该 reseller 有可用的
SharedIP。 - BoxBilling 通过 reseller 账号连接时,该 IP 已分配给这个 reseller。
- 创建 reseller 时,连接账号具有 admin 权限,并且可以访问相应 IP。
操作系统已经配置 IP,不等于 DirectAdmin 已登记该 IP 并向对应账号授权。BoxBilling 查询的是 DirectAdmin 中的账户和 IP 管理数据,不会直接读取服务器网卡。
如果 BoxBilling 使用 reseller 凭据创建普通用户,可以先在 DirectAdmin 中使用同一个 reseller 手动创建用户。如果手动创建时也无法选择 IP,应先修复 DirectAdmin 的 IP 分配,不必急着修改 BoxBilling。
检查 API 实际返回内容
旧 extension 通常通过 DirectAdmin 的 Legacy API 查询 IP。测试时应使用与 BoxBilling 完全相同的账号:
curl --silent --show-error \
--user 'API_USERNAME:LOGIN_KEY' \
'https://da.example.com:2222/CMD_API_SHOW_RESELLER_IPS'
请使用 DirectAdmin Login Key 或其他受限凭据,不要在命令历史、日志或工单中暴露管理员密码。
同时检查 HTTP 状态码和响应头:
curl --include --silent --show-error \
--user 'API_USERNAME:LOGIN_KEY' \
'https://da.example.com:2222/CMD_API_SHOW_RESELLER_IPS'
DirectAdmin 的版本、账号级别和配置不同,接口返回的字段也可能不同,不能假定它一定采用某种固定结构。检查时主要看以下内容:
- 返回的是 IP 数据还是登录失败信息;
- 是否返回
403、重定向页面或 HTML; - 响应中是否只有提示信息,没有 IP 列表;
- API 是否返回了 IP,但字段结构与 extension 的预期不一致;
- BoxBilling 是否连接到了正确的主机名、端口和 DirectAdmin 账号。
如果接口没有返回 IP,应检查 DirectAdmin 的权限或 IP 分配。如果接口已经返回 IP,BoxBilling 却仍抛出 There are no IPs,基本可以确定问题出在 extension 的响应解析逻辑。
检查 BoxBilling 连接配置
确认 BoxBilling 中的服务器配置满足以下条件:
- 端口通常是
2222,除非 DirectAdmin 使用了自定义端口; - HTTPS 设置与 DirectAdmin 的实际配置一致;
- 用户名中没有多余空格;
- 创建 reseller 时使用 admin,创建 user 时使用具有相应权限的 reseller;
- Login Key 允许调用账户和 IP 相关接口;
- 防火墙、代理或 WAF 没有将 API 响应替换为登录页面;
- API 请求连接的是正确的 DirectAdmin 节点,而不是负载均衡器中的其他服务器。
还需要查看 BoxBilling 和 DirectAdmin 的日志。不要只看最终的异常文本,还应记录 API 请求对应的 HTTP 状态码和原始响应,同时过滤密码、Login Key 和 Cookie。
修补旧 extension 的解析逻辑
如果 API 已返回 IP,而旧代码只检查某个固定字段,可以在抛出错误前加入兼容解析。下面的代码只是解析示例,具体写法需要根据 Directadmin.php 中请求方法的实际返回类型调整:
<?php
function extractDirectAdminIps(string $rawResponse): array
{
parse_str($rawResponse, $payload);
$values = $payload['list']
?? $payload['ips']
?? $payload['ip']
?? [];
if (!is_array($values)) {
$values = [$values];
}
$ips = [];
array_walk_recursive(
$values,
static function ($value) use (&$ips): void {
if (
is_string($value) &&
filter_var($value, FILTER_VALIDATE_IP) !== false
) {
$ips[] = $value;
}
}
);
return array_values(array_unique($ips));
}
调用时先保留原始响应,再进行解析:
$rawResponse = $this->request('CMD_API_SHOW_RESELLER_IPS');
$ips = extractDirectAdminIps($rawResponse);
if ($ips === []) {
throw new RuntimeException(
'DirectAdmin did not return an IP available to this account'
);
}
$ip = $ips[0];
这里的 $this->request() 只是占位写法,必须替换为 extension 中实际存在的请求方法,不能直接复制后假定它可以运行。
如果 extension 的请求方法已经将响应解析为数组,就不要再次调用 parse_str(),而应按照实际数组结构处理:
$response = $this->request('CMD_API_SHOW_RESELLER_IPS');
if (!is_array($response)) {
throw new RuntimeException('Unexpected DirectAdmin API response');
}
$values = $response['list']
?? $response['ips']
?? $response['ip']
?? [];
$values = is_array($values) ? $values : [$values];
$ips = array_values(array_filter(
$values,
static fn ($value): bool =>
is_string($value) &&
filter_var($value, FILTER_VALIDATE_IP) !== false
));
不要为了消除异常而直接硬编码服务器 IP。DirectAdmin 创建账户时,通常会将选中的 IP 作为 ip 参数传给账户创建接口,例如:
$params = [
'action' => 'create',
'username' => $username,
'email' => $email,
'passwd' => $password,
'passwd2' => $password,
'domain' => $domain,
'package' => $package,
'ip' => $ip,
'notify' => 'no',
];
如果硬编码的 IP 不属于当前 reseller,或者不是可用于托管域名的共享 IP,后续调用 CMD_API_ACCOUNT_USER 或 CMD_API_ACCOUNT_RESELLER 仍会失败。
推荐的排查顺序
- 使用 BoxBilling 配置中的同一账号登录 DirectAdmin。
- 手动创建同类型账户,确认该账号能够选择 IP。
- 调用
CMD_API_SHOW_RESELLER_IPS,查看原始响应。 - 如果 API 不返回 IP,修复 DirectAdmin 的 IP 状态、分配关系或 Login Key 权限。
- 如果 API 返回 IP,检查
Directadmin.php如何判断响应字段。 - 修补解析逻辑后,先通过测试订单或测试服务器创建账户。
- 确认账户创建、暂停、恢复、删除和套餐同步均正常,再用于正式订单。
注意事项
该 extension 长期未维护,问题可能不只出在 IP 查询。DirectAdmin 的认证方式、TLS 设置、API 响应格式和账户创建参数都可能已经变化。修复当前错误后,还需要完整测试账户的整个生命周期。
修改 extension 前应备份原文件,也不要在日志中记录明文密码。如果准备长期用于生产环境,更稳妥的做法是维护一个兼容当前 DirectAdmin API 的 fork,并针对“无 IP”“认证失败”“权限不足”和“响应格式异常”分别返回明确错误,不要统一抛出 There are no IPs。
备注:内容仅供参考。
