PAYATHON 2026

BoxBilling 连接 DirectAdmin 时提示服务器没有 IP

数据阿明

结论

这个错误通常不表示服务器网卡没有 IP,而是 BoxBilling 通过 DirectAdmin API 查询时,没有获取到当前登录账号可用于创建账户的 IP。

建议先检查两项:

  1. DirectAdmin 中是否已有可用 IP,并将其设置为 Shared,或分配给 BoxBilling 使用的 reseller。
  2. BoxBilling 使用的 DirectAdmin 账号是否有权查看该 IP。

如果 DirectAdmin 后台能看到可用 IP,但 BoxBilling 仍然报错,可能是旧版 extension 无法解析新版 DirectAdmin API 的响应格式。此时需要更新或修补 Directadmin.php。

先检查 DirectAdmin 的 IP 配置

以管理员身份进入 DirectAdmin 的 IP 管理页面,逐项确认:

  • 服务器 IP 已添加到 DirectAdmin。
  • IP 未处于异常或停用状态。
  • 创建普通 user 时,该 reseller 有可用的 Shared IP。
  • 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 仍会失败。

推荐的排查顺序

  1. 使用 BoxBilling 配置中的同一账号登录 DirectAdmin。
  2. 手动创建同类型账户,确认该账号能够选择 IP。
  3. 调用 CMD_API_SHOW_RESELLER_IPS,查看原始响应。
  4. 如果 API 不返回 IP,修复 DirectAdmin 的 IP 状态、分配关系或 Login Key 权限。
  5. 如果 API 返回 IP,检查 Directadmin.php 如何判断响应字段。
  6. 修补解析逻辑后,先通过测试订单或测试服务器创建账户。
  7. 确认账户创建、暂停、恢复、删除和套餐同步均正常,再用于正式订单。

注意事项

该 extension 长期未维护,问题可能不只出在 IP 查询。DirectAdmin 的认证方式、TLS 设置、API 响应格式和账户创建参数都可能已经变化。修复当前错误后,还需要完整测试账户的整个生命周期。

修改 extension 前应备份原文件,也不要在日志中记录明文密码。如果准备长期用于生产环境,更稳妥的做法是维护一个兼容当前 DirectAdmin API 的 fork,并针对“无 IP”“认证失败”“权限不足”和“响应格式异常”分别返回明确错误,不要统一抛出 There are no IPs。

备注:内容仅供参考。