---
title: Enabling DNSSEC
path: tutorials/enable-dnssec
status: published
---

# Enabling DNSSEC

A step-by-step guide to signing a zone with DNSSEC. For the concepts and cryptographic background, see [DNSSEC](../concepts/dnssec.md).

**Base path:** `/api/v1/domains/{domain_id}/dnssec`
**Required permission:** `dnssec:*` on the domain

## Prerequisites

- Zone is `active` (validation completed).
- You have access to your registrar's admin panel to publish DS records.
- A DNSSEC-validating resolver is available for testing (`1.1.1.1`, `8.8.8.8`, and most modern resolvers validate).

## 1. Check current status

```bash
curl https://scaidns.scailabs.ai/api/v1/domains/$DOMAIN_ID/dnssec \
  -H "X-API-Key: $SCAIDNS_API_KEY"
```

```python
resp = httpx.get(
    f"https://scaidns.scailabs.ai/api/v1/domains/{DOMAIN_ID}/dnssec",
    headers={"X-API-Key": os.environ["SCAIDNS_API_KEY"]},
)
print(resp.json())
```

```typescript
const resp = await fetch(
  `https://scaidns.scailabs.ai/api/v1/domains/${domainId}/dnssec`,
  { headers: { "X-API-Key": process.env.SCAIDNS_API_KEY! } }
);
console.log(await resp.json());
```

If `enabled: false`, proceed to step 2. If already enabled, skip ahead to rotation or verification.

## 2. Enable with algorithm 13

```bash
curl -X POST https://scaidns.scailabs.ai/api/v1/domains/$DOMAIN_ID/dnssec/enable \
  -H "X-API-Key: $SCAIDNS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"algorithm": 13}'
```

```python
resp = httpx.post(
    f"https://scaidns.scailabs.ai/api/v1/domains/{DOMAIN_ID}/dnssec/enable",
    headers={"X-API-Key": os.environ["SCAIDNS_API_KEY"]},
    json={"algorithm": 13},
)
result = resp.json()
for ds in result["ds_records"]:
    print(f"{ds['key_tag']} {ds['algorithm']} {ds['digest_type']} {ds['digest']}")
```

```typescript
const resp = await fetch(
  `https://scaidns.scailabs.ai/api/v1/domains/${domainId}/dnssec/enable`,
  {
    method: "POST",
    headers: {
      "X-API-Key": process.env.SCAIDNS_API_KEY!,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ algorithm: 13 }),
  },
);
const result = await resp.json();
for (const ds of result.ds_records) {
  console.log(ds.key_tag, ds.algorithm, ds.digest_type, ds.digest);
}
```

### Algorithm selection

| Algorithm | Number | When to use |
|-----------|--------|------------|
| ECDSA P-256/SHA-256 | 13 | **Default choice.** Small, fast, universally supported |
| Ed25519 | 15 | Modern; use if your TLD and registrar support it |
| RSA/SHA-256 | 8 | Legacy compatibility; avoid for new zones |

When in doubt, use 13.

### Response

```json
{
  "enabled": true,
  "algorithm": 13,
  "ksk": {
    "key_tag": 12345,
    "algorithm": 13,
    "public_key": "...",
    "created_at": "2026-04-23T10:00:00Z",
    "expires_at": "2027-04-23T10:00:00Z"
  },
  "zsk": {
    "key_tag": 67890,
    "algorithm": 13,
    "public_key": "...",
    "created_at": "2026-04-23T10:00:00Z",
    "expires_at": "2026-05-23T10:00:00Z"
  },
  "ds_records": [
    {
      "key_tag": 12345,
      "algorithm": 13,
      "digest_type": 2,
      "digest": "A1B2C3D4E5F6..."
    }
  ]
}
```

The zone is now signed at the nameserver level. DNSSEC validation will **fail** until you publish the DS records at the registrar, because the chain of trust isn't established yet.

## 3. Publish DS records at the registrar

Log into your registrar's admin panel and find the DS records section. It's often under "DNSSEC" or "Security."

For each DS record in the response, enter:

- **Key Tag:** the integer `key_tag`.
- **Algorithm:** the integer `algorithm`.
- **Digest Type:** the integer `digest_type` (2 = SHA-256, typical).
- **Digest:** the `digest` hex string.

Save. The registrar pushes these to the parent TLD zone on its own schedule — minutes to hours, occasionally longer.

Watch for propagation:

```bash
dig +short DS example.com @1.1.1.1
```

When the digest shows up, move on.

## 4. Confirm DS is published

Tell ScaiDNS that the chain is complete:

```bash
curl -X POST https://scaidns.scailabs.ai/api/v1/domains/$DOMAIN_ID/dnssec/confirm-ds-published \
  -H "X-API-Key: $SCAIDNS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "ds_records": [
      {"key_tag": 12345, "algorithm": 13, "digest_type": 2, "digest": "A1B2C3D4E5F6..."}
    ]
  }'
```

This step exists for audit purposes — the actual validation happens at resolvers, but recording confirmation gives you a trail of when DNSSEC went live for each zone.

## 5. Verify end-to-end

```bash
dig +dnssec +short www.example.com @1.1.1.1
dig +dnssec +multi www.example.com @1.1.1.1 | grep -E 'status|flags'
```

In the full response, look for:

- Header `flags` containing `ad` — the resolver validated the answer.
- No `ad` and no errors — the resolver doesn't validate (unusual), but your zone is still signed.
- `SERVFAIL` — validation failed. Something in the chain is broken.

Alternatively, use an online analyzer: `https://dnssec-analyzer.verisignlabs.com/example.com`. Errors at each level explain where the chain breaks.

## Rotating keys

### ZSK rotation (safe any time)

```bash
curl -X POST https://scaidns.scailabs.ai/api/v1/domains/$DOMAIN_ID/dnssec/rotate-zsk \
  -H "X-API-Key: $SCAIDNS_API_KEY"
```

Transparent to resolvers and registrars. ScaiDNS generates a new ZSK, signs the zone with both during the rollover window, then retires the old one.

### KSK rotation (requires registrar coordination)

```bash
curl -X POST https://scaidns.scailabs.ai/api/v1/domains/$DOMAIN_ID/dnssec/rotate-ksk \
  -H "X-API-Key: $SCAIDNS_API_KEY"
```

Response includes the new DS record. Steps to complete:

1. Add the new DS record at the registrar (keep the old one).
2. Wait for propagation.
3. Call `/confirm-ds-published` with the new DS record.
4. After the overlap period, the old KSK is retired.
5. Remove the old DS record at the registrar.

**Do not remove the old DS record before the overlap completes.** Validators may still be caching answers signed by the old key.

## Disabling DNSSEC

**Remove the DS records at the registrar first.** Then disable in ScaiDNS:

```bash
curl -X POST https://scaidns.scailabs.ai/api/v1/domains/$DOMAIN_ID/dnssec/disable \
  -H "X-API-Key: $SCAIDNS_API_KEY"
```

If you disable with DS still at the registrar, resolvers see DS but no DNSKEY in the zone and return SERVFAIL.

## Troubleshooting

| Symptom | Check |
|---------|-------|
| `dig +dnssec` shows no `ad` flag | Resolver may not be validating. Try `1.1.1.1` or `9.9.9.9` |
| Online analyzer shows "no DS at parent" | Registrar hasn't propagated the DS yet; wait or re-submit |
| Online analyzer shows "DS digest mismatch" | DS at registrar doesn't match any KSK. Regenerate DS via `GET /dnssec` and republish |
| `SERVFAIL` from validating resolvers | Broken chain. Often the DS at registrar references a retired KSK |
| `SERVFAIL` right after KSK rotation | Normal briefly; resolvers still have old key cached |

## What's next

- [DNSSEC reference](../reference/dnssec.md) — endpoint details.
- [DNSSEC concepts](../concepts/dnssec.md) — cryptographic background.
