# Custom domains (archived) (/legacy/custom-domains)



<Callout type="warn" title="Archived: not available today">
  This page describes the domain flow of the retired sites product (site IDs,
  IPFS gateways, site verification records). Custom domains for Alternate Clouds
  services are not available yet and none of these steps work on the current
  platform. The page is kept for reference and will be replaced when the feature
  ships. See [What changed from af](/legacy).
</Callout>

Bring your own domain from any registrar (GoDaddy, Namecheap, Cloudflare, etc.) and point it to your Alternate Clouds services.

## Overview [#overview]

By default, sites are accessible via:

* **IPFS**: `https://gateway.ipfs.io/ipfs/{CID}`
* **Arweave**: `https://arweave.net/{TX_ID}`
* **Filecoin**: `https://ipfs.io/ipfs/{CID}`

Custom domains provide:

* Branded URLs (e.g., `https://example.com`)
* Easier to remember and share
* SEO benefits
* Professional appearance
* Automatic SSL/TLS certificates via Let's Encrypt

## Adding a Custom Domain [#adding-a-custom-domain]

### Step 1: Create Domain [#step-1-create-domain]

Use the GraphQL API or SDK to add your domain:

```graphql
mutation {
  createDomain(input: {
    hostname: "example.com"
    siteId: "site-123"
    verificationMethod: TXT
  }) {
    id
    hostname
    txtVerificationToken
    verified
  }
}
```

**Verification Methods:**

* `TXT` - TXT record verification (works with root domains and subdomains)
* `CNAME` - CNAME record verification (subdomains only)
* `A` - A record verification (points to platform IP)

### Step 2: Configure DNS Records [#step-2-configure-dns-records]

Based on your chosen verification method, add the appropriate DNS record at your domain registrar:

## DNS configuration methods [#dns-configuration-methods]

### Method 1: TXT Record Verification (Recommended) [#method-1-txt-record-verification-recommended]

Add a TXT record to verify domain ownership:

```
Type: TXT
Name: @  (or your hostname)
Value: af-site-verification=xxxxx  (from createDomain response)
TTL: 3600
```

**Example:**

```graphql
mutation {
  createDomain(input: {
    hostname: "example.com"
    siteId: "site-123"
    verificationMethod: TXT
  }) {
    id
    txtVerificationToken  # Use this value
  }
}
```

**Pros:**

* Works with root domains and subdomains
* Non-intrusive (doesn't affect current DNS)
* Easy to verify

**Use Case:** Best for initial verification before changing DNS routing

### Method 2: CNAME Record Verification [#method-2-cname-record-verification]

Point your subdomain to the platform:

```
Type: CNAME
Name: www  (or subdomain)
Value: cname.alternatefutures.ai
TTL: 3600
```

**Example:**

```graphql
mutation {
  createDomain(input: {
    hostname: "www.example.com"
    siteId: "site-123"
    verificationMethod: CNAME
  }) {
    id
    expectedCname  # Points to platform
  }
}
```

**Pros:**

* Verifies and routes traffic in one step
* Automatic deployment updates
* CDN benefits

**Cons:**

* Only works with subdomains (not root domains)

**Use Case:** Best for `www.example.com` or other subdomains

### Method 3: A Record Verification [#method-3-a-record-verification]

Point directly to the platform IP:

```
Type: A
Name: @  (for root domain)
Value: [Platform IP Address]
TTL: 3600
```

**Example:**

```graphql
mutation {
  createDomain(input: {
    hostname: "example.com"
    siteId: "site-123"
    verificationMethod: A
  }) {
    id
    expectedARecord  # Platform IP
  }
}
```

**Pros:**

* Works with root domains
* Verifies and routes in one step
* Fast DNS resolution

**Use Case:** Best for apex/root domains like `example.com`

### Step 3: Verify Domain [#step-3-verify-domain]

After adding DNS records, verify your domain:

```graphql
mutation {
  verifyDomain(domainId: "domain-123")
}
```

This checks DNS propagation and updates the domain status. You may need to wait a few minutes for DNS changes to propagate globally.

**Check verification status:**

```graphql
query {
  domain(id: "domain-123") {
    id
    hostname
    verified
    txtVerificationStatus
    dnsCheckAttempts
    lastDnsCheck
  }
}
```

## SSL/TLS Certificates [#ssltls-certificates]

Once your domain is verified, provision an SSL certificate:

```graphql
mutation {
  provisionSsl(
    domainId: "domain-123"
    email: "admin@example.com"
  ) {
    id
    sslStatus
    sslIssuedAt
    sslExpiresAt
  }
}
```

**SSL Features:**

* Automatic Let's Encrypt certificate provisioning
* Auto-renewal 30 days before expiry
* HTTP-01 and DNS-01 ACME challenges supported
* Free SSL for all domains

**Certificate Process:**

1. Domain must be verified first
2. Request SSL certificate via `provisionSsl` mutation
3. Wait 5-10 minutes for Let's Encrypt validation
4. Certificate automatically installed and renewed

## Domain status [#domain-status]

**Verification Status:**

* `PENDING` - DNS not yet propagated
* `VERIFIED` - Domain ownership confirmed
* `FAILED` - DNS misconfigured or verification failed

**SSL Status:**

* `NONE` - No certificate requested
* `PENDING` - Certificate being provisioned
* `ACTIVE` - SSL certificate active and working
* `FAILED` - Certificate provisioning failed

**Check status:**

```graphql
query {
  domain(id: "domain-123") {
    verified
    txtVerificationStatus
    sslStatus
    sslExpiresAt
  }
}
```

## Managing multiple domains [#managing-multiple-domains]

### List all domains [#list-all-domains]

Get all domains for a site:

```graphql
query {
  site(id: "site-123") {
    id
    domains {
      id
      hostname
      verified
      sslStatus
      createdAt
    }
  }
}
```

### Set the primary domain [#set-the-primary-domain]

Set a verified domain as the primary domain for your site:

```graphql
mutation {
  setPrimaryDomain(
    siteId: "site-123"
    domainId: "domain-123"
  ) {
    id
    primaryDomain {
      hostname
    }
  }
}
```

**Requirements:**

* Domain must be verified
* Only verified domains can be primary

### Remove a domain [#remove-a-domain]

Remove a custom domain:

```graphql
mutation {
  removeDomain(domainId: "domain-123")
}
```

**Note:** Cannot remove a domain that is set as primary. Set a different primary domain first.

### Common multi-domain setup [#common-multi-domain-setup]

Add multiple domains for different purposes:

* **Primary domain**: `example.com`
* **WWW subdomain**: `www.example.com`
* **Regional domains**: `example.co.uk`, `example.ca`
* **Staging domains**: `staging.example.com`

Each domain can have its own SSL certificate and verification method.

## Troubleshooting [#troubleshooting]

### Verification failing [#verification-failing]

**1. Check DNS propagation:**

```bash
dig example.com TXT
dig www.example.com CNAME
dig example.com A
```

DNS changes can take 5 minutes to 48 hours to propagate globally.

**2. Verify correct DNS values:**

For TXT verification:

```bash
dig example.com TXT | grep "af-site-verification"
```

For CNAME verification:

```bash
dig www.example.com CNAME
# Should show: cname.alternatefutures.ai
```

For A record verification:

```bash
dig example.com A
# Should show platform IP
```

**3. Common verification issues:**

* DNS not propagated yet (wait 5-60 minutes)
* Wrong record type (TXT vs CNAME vs A)
* Incorrect hostname (@ vs www vs subdomain)
* Typo in verification token
* TTL too high (recommended: 3600 seconds)

**4. Check verification attempts:**

```graphql
query {
  domain(id: "domain-123") {
    dnsCheckAttempts
    lastDnsCheck
    txtVerificationStatus
  }
}
```

If attempts > 10 and still failing, double-check DNS configuration.

### SSL certificate issues [#ssl-certificate-issues]

**Check certificate status:**

```graphql
query {
  domain(id: "domain-123") {
    sslStatus
    sslIssuedAt
    sslExpiresAt
  }
}
```

**Common issues:**

1. **Domain not verified**
   * Error: "Domain must be verified before provisioning SSL"
   * Solution: Complete DNS verification first

2. **Certificate provisioning stuck on PENDING**
   * DNS not fully propagated
   * CAA records blocking Let's Encrypt
   * Port 80/443 not accessible for HTTP-01 challenge
   * Wait 10-15 minutes and check again

3. **Certificate expired**
   * Certificates auto-renew 30 days before expiry
   * Check: `sslExpiresAt` field
   * Re-provision if needed: Run `provisionSsl` mutation again

**Test SSL certificate:**

```bash
curl -vI https://example.com
openssl s_client -connect example.com:443 -servername example.com
```

### DNS provider-specific issues [#dns-provider-specific-issues]

**Cloudflare:**

* Disable "orange cloud" proxy for verification
* Use "DNS only" (grey cloud) mode initially
* Re-enable proxy after verification completes

**GoDaddy:**

* DNS propagation can be slow (24-48 hours)
* Use shorter TTL values (600-3600 seconds)

**Namecheap:**

* Use `@` for root domain TXT records
* Use `www` for CNAME records (not `www.example.com`)

**Google Domains / Cloud DNS:**

* Include trailing dot in CNAME if required
* Example: `cname.alternatefutures.ai.`

## Supported DNS Providers [#supported-dns-providers]

Tested and working with:

* **Cloudflare** - Fast propagation (5-15 min), excellent DNS management
* **Namecheap** - Good propagation (30-60 min)
* **GoDaddy** - Slower propagation (2-24 hours)
* **Google Domains** - Fast and reliable
* **AWS Route53** - Enterprise-grade, fast propagation
* **DigitalOcean DNS** - Fast and simple
* **Vercel DNS** - Fast propagation

All major DNS providers support TXT, CNAME, and A records needed for domain verification.

## Web3 Domains [#web3-domains]

### ArNS (Arweave Name System) [#arns-arweave-name-system]

Register permanent domains on Arweave:

```graphql
mutation {
  registerArnsName(
    siteId: "site-123"
    arnsName: "my-site"
  ) {
    name
    transactionId
    contentId
  }
}
```

**Features:**

* Permanent, immutable domains
* Stored on Arweave blockchain
* Access via: `my-site.arweave.net`

### ENS (Ethereum Name System) [#ens-ethereum-name-system]

Link ENS domains to your content:

```graphql
mutation {
  linkEnsDomain(
    siteId: "site-123"
    ensName: "mysite.eth"
  ) {
    ensName
    contentHash
    resolverAddress
  }
}
```

**Features:**

* Ethereum-based domain names
* Decentralized DNS alternative
* Access via ENS-compatible browsers

### IPNS (IPFS Name System) [#ipns-ipfs-name-system]

Create mutable IPNS pointers:

```graphql
mutation {
  createIpnsName(
    siteId: "site-123"
    ipnsName: "my-ipns-site"
  ) {
    ipnsName
    ipnsHash
    currentCid
  }
}
```

**Features:**

* Mutable pointers to IPFS content
* Update content without changing address
* Access via: `/ipns/{hash}`

## Complete example workflow [#complete-example-workflow]

Here's a complete example of adding a custom domain with SSL:

```graphql
# Step 1: Create domain with TXT verification
mutation CreateDomain {
  createDomain(input: {
    hostname: "example.com"
    siteId: "site-abc123"
    verificationMethod: TXT
  }) {
    id
    hostname
    txtVerificationToken
  }
}

# Response:
# {
#   "id": "domain-xyz789",
#   "hostname": "example.com",
#   "txtVerificationToken": "af-site-verification=abc123def456"
# }

# Step 2: Add DNS TXT record at your registrar
# Type: TXT
# Name: @
# Value: af-site-verification=abc123def456
# TTL: 3600

# Step 3: Wait 5-60 minutes for DNS propagation
# Test with: dig example.com TXT

# Step 4: Verify domain
mutation VerifyDomain {
  verifyDomain(domainId: "domain-xyz789")
}

# Step 5: Check verification status
query CheckDomain {
  domain(id: "domain-xyz789") {
    verified
    txtVerificationStatus
  }
}

# Step 6: Provision SSL certificate
mutation ProvisionSSL {
  provisionSsl(
    domainId: "domain-xyz789"
    email: "admin@example.com"
  ) {
    sslStatus
    sslIssuedAt
    sslExpiresAt
  }
}

# Step 7: Wait 5-10 minutes for Let's Encrypt
# Your site is now live at https://example.com!

# Step 8: Set as primary domain (optional)
mutation SetPrimary {
  setPrimaryDomain(
    siteId: "site-abc123"
    domainId: "domain-xyz789"
  ) {
    id
    primaryDomain {
      hostname
    }
  }
}
```

## API reference [#api-reference]

### Mutations [#mutations]

**createDomain** - Add a custom domain

* Input: `CreateDomainInput`
* Returns: `Domain`

**verifyDomain** - Verify domain ownership

* Input: `domainId: ID!`
* Returns: `Boolean`

**provisionSsl** - Request SSL certificate

* Input: `domainId: ID!`, `email: String!`
* Returns: `Domain`

**setPrimaryDomain** - Set primary domain for site

* Input: `siteId: ID!`, `domainId: ID!`
* Returns: `Site`

**removeDomain** - Remove custom domain

* Input: `domainId: ID!`
* Returns: `Boolean`

### Queries [#queries]

**domain** - Get domain by ID

* Input: `id: ID!`
* Returns: `Domain`

**domains** - List all domains for a site

* Input: `siteId: ID!`
* Returns: `[Domain!]!`

## Next steps [#next-steps]

* [Projects](/guides/projects) - Organize services and members
* [Troubleshooting](/troubleshooting) - DNS and certificate issues
* [Quick start](/guides/quickstart) - Deploy a service to point a domain at
