Platform
ScaiWave ScaiGrid ScaiCore ScaiBot ScaiDrive ScaiKey Models Tools & Services
Solutions
Organisations Developers Internet Service Providers Managed Service Providers AI-in-a-Box
Resources
Support Documentation Blog Downloads
Company
About Research Careers Investment Opportunities Contact
Log in

Issue an ACME Wildcard Certificate

Get a Let's Encrypt wildcard certificate (*.acme.example) using DNS-01 challenges. Wildcards require DNS-01 — Let's Encrypt won't sign one via HTTP-01. ScaiVault handles the DNS dance automatically once you've connected a DNS provider.

What you need#

  • A domain whose authoritative nameservers you control (or that's hosted on a supported DNS provider).
  • A ScaiVault token with pki:issue and pki:admin.
  • A reachable HTTPS endpoint for renewal-status webhooks (optional but recommended).

1. Configure a DNS provider#

ScaiVault writes the _acme-challenge TXT record via this provider during the challenge. Use whichever your zone lives on; this example uses Route 53.

bash
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# Store AWS credentials in ScaiVault
curl -X PUT https://scaivault.scailabs.ai/v1/secrets/infra/aws/route53-acme \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "data": {
      "access_key_id": "AKIA...",
      "secret_access_key": "..."
    },
    "secret_type": "json"
  }'

# Configure the DNS provider
curl -X POST https://scaivault.scailabs.ai/v1/dns/providers \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "route53-acme",
    "provider_type": "route53",
    "credentials": {
      "access_key_id": "AKIA...",
      "secret_access_key": "..."
    },
    "managed_zones": ["acme.example", "*.acme.example"],
    "verify": true
  }'
# -> {"id": "dnsp_abc", "is_active": true, ...}

The IAM credentials need a minimum policy — see DNS Providers Reference. The verify: true flag refuses to create the provider if it can't list zones, so you'll know immediately if the credentials are wrong.

2. Register an ACME account#

Use the staging environment for your first attempt — Let's Encrypt's staging server has looser rate limits and you don't want to burn through your production quota debugging signatures.

bash
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
curl -X POST https://scaivault.scailabs.ai/v1/pki/acme/accounts \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "letsencrypt-staging",
    "provider": "letsencrypt",
    "environment": "staging",
    "email": "certs@acme.example"
  }'
# -> {"id": "acme_staging_abc", "status": "active", ...}

ScaiVault generates the ACME account key and registers with Let's Encrypt. The account key stays in ScaiVault.

3. Issue a wildcard cert (staging)#

bash
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
curl -X POST https://scaivault.scailabs.ai/v1/pki/acme/issue \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "account_id": "acme_staging_abc",
    "domains": ["*.acme.example", "acme.example"],
    "challenge_type": "dns-01",
    "auto_renew": true
  }'
# -> {"order_id": "acme_order_xyz", "status": "pending", ...}

Including the apex acme.example alongside *.acme.example is common — wildcards don't match the apex, so cover it explicitly if you want both.

Poll:

bash
1
2
3
4
5
6
7
8
while true; do
  status=$(curl -s -H "Authorization: Bearer $TOKEN" \
    https://scaivault.scailabs.ai/v1/pki/acme/orders/acme_order_xyz | jq -r '.status')
  echo "$(date +%H:%M:%S) $status"
  [ "$status" = "valid" ] && break
  [ "$status" = "invalid" ] && break
  sleep 5
done

Typical sequence:

  • pending (~5s) — ScaiVault writes the TXT record.
  • pending (~30-60s) — waiting for DNS propagation.
  • processing (~5s) — telling ACME the challenge is ready.
  • valid — done. The cert is issued.

Get the certificate:

bash
1
2
3
4
5
6
7
order=$(curl -s -H "Authorization: Bearer $TOKEN" \
  https://scaivault.scailabs.ai/v1/pki/acme/orders/acme_order_xyz)
cert_id=$(echo "$order" | jq -r '.certificate_id')

curl -s -H "Authorization: Bearer $TOKEN" \
  https://scaivault.scailabs.ai/v1/pki/certificates/$cert_id/pem \
  | jq -r '.certificate_pem'

The cert chains back to Let's Encrypt's staging root — your browser will reject it as untrusted. That's expected; staging certs are intentionally not trusted by public roots.

4. Promote to production#

Repeat steps 2 and 3 with environment: "production". Same code, real cert.

bash
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
curl -X POST https://scaivault.scailabs.ai/v1/pki/acme/accounts \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "letsencrypt-production",
    "provider": "letsencrypt",
    "environment": "production",
    "email": "certs@acme.example"
  }'

curl -X POST https://scaivault.scailabs.ai/v1/pki/acme/issue \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "account_id": "acme_production_abc",
    "domains": ["*.acme.example", "acme.example"],
    "challenge_type": "dns-01",
    "auto_renew": true
  }'

Watch out for Let's Encrypt's rate limits — 50 certs per registered domain per week. If you're testing in production and hit this, switch back to staging.

5. Get the cert into your edge#

For an ingress (nginx, Traefik, HAProxy) running in Kubernetes, fetch the cert at startup and refresh periodically. A small init script and CronJob:

bash
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
#!/bin/sh
# fetch-cert.sh — write the current ACME cert to disk

CERT_ID="${CERT_ID:?required}"  # known after first issuance

resp=$(curl -fsS -H "Authorization: Bearer $SCAIVAULT_TOKEN" \
  "https://scaivault.scailabs.ai/v1/pki/certificates/$CERT_ID/pem")

mkdir -p /etc/tls
echo "$resp" | jq -r '.certificate_pem' > /etc/tls/fullchain.pem

# Get the private key — only works if role stores keys, or if ACME-issued
curl -fsS -X POST -H "Authorization: Bearer $SCAIVAULT_TOKEN" \
  "https://scaivault.scailabs.ai/v1/pki/certificates/$CERT_ID/private-key" \
  | jq -r '.private_key_pem' > /etc/tls/privkey.pem

chmod 600 /etc/tls/privkey.pem

# Reload nginx if it's running
[ -f /var/run/nginx.pid ] && nginx -s reload

Run this on a CronJob every 6 hours. Idempotent — same cert, no-op reload. When the underlying cert renews (~30 days before expiry), the next CronJob run picks up the new version and triggers a reload.

6. Auto-renewal verification#

auto_renew: true means ScaiVault will renew automatically ~30 days before not_after. Subscribe to certificate.renewed events so you can confirm the renewal worked and your edge has refreshed:

bash
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
curl -X POST https://scaivault.scailabs.ai/v1/subscriptions \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "wildcard-renewals",
    "paths": ["pki/certificates/cert_*"],
    "events": ["certificate.renewed", "certificate.renewal_failed"],
    "delivery": {
      "type": "webhook",
      "url": "https://ops.acme.example/scaivault/cert-events",
      "secret": "whsec_..."
    }
  }'

Alert on certificate.renewal_failed. ScaiVault retries on backoff, but persistent failures (DNS provider creds expired, Let's Encrypt rate-limit hit) need human intervention.

7. Force an early renewal (rare)#

If you need to renew before the 30-day window — e.g., emergency rotation, an audit requirement — trigger it:

bash
1
2
curl -X POST https://scaivault.scailabs.ai/v1/pki/certificates/cert_abc/renew \
  -H "Authorization: Bearer $TOKEN"

Same ACME flow, new cert issued. Watch out: every renewal counts against the Let's Encrypt rate limit.

What you have now#

  • A wildcard cert covering *.acme.example and acme.example.
  • Auto-renewal ~30 days before expiry, with event notification.
  • A DNS provider configured to handle challenges for the zone.
  • Edge servers reloading the cert without manual intervention.

Common failure modes#

Order stays pending for minutes. DNS propagation is slow. Check authoritative nameservers directly: dig +short TXT _acme-challenge.acme.example @<authoritative-ns>. If the record isn't there, your DNS provider integration didn't write it — check the provider with POST /v1/dns/providers/{id}/verify and the audit log for dns_record_create.

Order goes invalid quickly. Look at the order's challenges[].error field. Common: domain in the order isn't in any DNS provider's managed_zones. Add the zone to a provider.

Rate-limit error from Let's Encrypt. Switch back to staging, or wait. The error envelope tells you when you can retry: "backend_rate_limited" with "retry_after".

Wildcard doesn't work for the apex. *.acme.example doesn't match acme.example. Include both in domains, as in step 3.

What's next#

Updated 2026-05-17 13:26:51 View source (.md) rev 1