nextcloud/all-in-one · error · \Exception

Unexpected response from deSEC while checking domain ownersh

Error message

Unexpected response from deSEC while checking domain ownership (HTTP {code}): {body}

What it means

GET {desecApiBase}/domains/{domain}/ returned a status other than 200 (owned) or 404 (not owned) during the ownership check. Realistic codes: 401 (token expired or revoked between calls), 429 (rate limit), 5xx (deSEC incident). The raw body is embedded.

Source

Thrown at php/src/Desec/DesecManager.php:362

        try {
            $res = $this->guzzleClient->get($this->configurationManager->desecApiBase . '/domains/' . $domain . '/', [
                'headers' => ['Authorization' => 'Token ' . $token],
            ]);
        } catch (TransferException $e) {
            throw new \Exception('Could not reach the deSEC API: ' . $e->getMessage());
        }

        $code = $res->getStatusCode();

        if ($code === 200) {
            return true;
        }

        if ($code === 404) {
            return false;
        }

        throw new \Exception('Unexpected response from deSEC while checking domain ownership (HTTP ' . $code . '): ' . $res->getBody()->getContents());
    }

    /**
     * Creates a wildcard CNAME rrset (*.domain → domain.) for a newly registered domain.
     * Errors are logged but do not abort the overall registration.
     */
    private function createWildcardCname(string $token, string $domain): void {
        try {
            $res = $this->guzzleClient->post($this->configurationManager->desecApiBase . '/domains/' . $domain . '/rrsets/', [
                'headers' => ['Authorization' => 'Token ' . $token],
                'json'    => [
                    'subname' => '*',
                    'type'    => 'CNAME',
                    'ttl'     => 3600,
                    'records' => [$domain . '.'],
                ],
            ]);
        } catch (TransferException $e) {

View on GitHub (pinned to 6b788eec5e)

Solutions

  1. On 401, re-authenticate with loginAccount() to mint a fresh token and restart the flow
  2. On 429, back off and retry the ownership check
  3. Read the embedded body for the exact API message
  4. Confirm the account password was not reset at desec.io mid-flow
Defensive patterns

Strategy: try-catch

Validate before calling

// Verify the token is still live before the ownership-sensitive flow
$res = $guzzle->get("$apiBase/domains/", ['headers' => ['Authorization' => "Token $token"], 'http_errors' => false]);
if ($res->getStatusCode() === 401) {
    $token = $manager->loginAccount($email, $password); // mint a fresh token
}

Try / catch

try {
    $manager->registerDomain($token, $slug);
} catch (\Exception $e) {
    if (str_contains($e->getMessage(), 'checking domain ownership')) {
        if (str_contains($e->getMessage(), '(HTTP 401)')) { $token = relogin(); return retry(); }
        if (str_contains($e->getMessage(), '(HTTP 429)')) { scheduleRetry(30); return; }
    }
    throw $e;
}

Prevention

When it happens

Trigger: The bearer token became invalid between the failed POST /domains/ and this GET (e.g. user reset the deSEC password, which revokes tokens); 429 or 5xx from the API.

Common situations: Long-lived token invalidated by a password change on desec.io; rate limiting after repeated attempts; desec.io instability.

Related errors


AI-assisted analysis of nextcloud/all-in-one@6b788eec5e (2026-08-21). Data as JSON: /api/errors/42d90a8ea62c5124. Report an issue: GitHub.