Skip to main content

Migrate DNS from Cloudflare to CDB

Moving DNS from Cloudflare to CDB Managed DNS means exporting the zone file, importing records into CDB, then switching authoritative nameservers at the domain registrar. Nameserver changes can take up to 24 hours to propagate, so plan the cutover for a low-traffic window.

TTL preparation

Lower the TTL on critical DNS records in Cloudflare before the cutover. During the migration window, some resolvers still serve records from Cloudflare while the nameserver change propagates. A shorter TTL limits how long those resolvers cache the records, so any corrections to the zone take effect faster.

  1. Log in to the Cloudflare dashboard and select the domain.
  2. Navigate to DNS > Records.
  3. Edit each critical record and set the TTL to 10 minutes (or the lowest available value).
  4. Wait for the current TTL to expire before continuing, so old cached records stop being served.

If DNSSEC is enabled on the domain in Cloudflare, disable it before switching nameservers. Changing nameservers while DNSSEC is active causes DNSSEC validation to fail, and the domain stops resolving for many clients. Disable DNSSEC in Cloudflare, remove the DS record at the registrar, wait for the DS record TTL to expire, then proceed with the nameserver switch.

  1. 1

    Export DNS records from Cloudflare

    Download a BIND zone file from Cloudflare so the same records can be imported into CDB.

    1. In the Cloudflare dashboard, select the domain and navigate to DNS > Records.
    2. Click Export to download the DNS records as a BIND zone file (.txt format).

    Before importing the zone file, review records that were proxied through Cloudflare and confirm their values point to the intended origin server or Orange's CDN endpoint.

  2. 2

    Create a DNS zone in CDB

    Create a zone in the CDB Technical Web Portal so imported records have a destination. The zone creation guide covers the same flow with screenshots.

    1. Navigate to DNS in the Customer Portal and click Add zone.
    2. Enter the domain name and click Confirm.
    3. CDB scans and displays any existing DNS records it can detect. Review them, then click Confirm.

    Check the Skip scanning box to import all records manually from the Cloudflare zone file in the next step.

  3. 3

    Import DNS records

    Import the Cloudflare zone file into the new CDB zone, then review every record before changing nameservers.

    1. In the All zones tab, open the newly created zone.
    2. Click Import records.
    3. In the pop-up, either paste the contents of the Cloudflare zone file directly, or rename the exported .txt file to .zone and click Upload zone file to upload it.
    4. Click Import.

    Review the imported records and confirm they look correct. Pay particular attention to records that were previously proxied through Cloudflare and verify that they point to the intended origin server or Orange's CDN endpoint.

    Cloudflare supports CNAME flattening, which lets a CNAME record exist at the domain root (example.com). Standard DNS does not permit CNAME at the apex. Root records that used CNAME flattening in Cloudflare may appear differently in the zone file or after import — check them carefully and confirm they resolve to the expected address.

    Only DNS records are migrated. Cloudflare CDN, proxy rules, WAF, page rules, redirects, Workers, and SSL/TLS settings are not transferred automatically. Configure those services separately in CDB or another provider after the DNS cutover.

  4. 4

    Update nameservers at the registrar

    Point the domain to CDB nameservers by updating the nameserver settings at the domain registrar (the company where the domain was purchased or is managed).

    Update the nameserver settings at the domain registrar, not inside Cloudflare. Adding or editing NS records inside the Cloudflare dashboard does not change which DNS provider is authoritative for the domain, because the registrar controls that setting.

    Set the nameservers to the following CDB values:

    NameserverValue
    NS 1ns1.gcorelabs.net
    NS 2ns2.gcdn.services

    Look for a Nameservers or DNS section in the registrar control panel. The exact steps vary by registrar.

  5. 5

    Verify propagation

    DNS changes can take up to 24 hours to propagate globally. Check progress in both of these places:

    • Use a DNS propagation checker to verify that the domain's NS records resolve to CDB nameservers worldwide.
    • In the Customer Portal, a notification appears on the DNS zone until propagation is complete. This is expected and clears automatically.

    Once the NS records resolve to CDB, verify that critical migrated records return the expected values:

    dig A yourdomain.com
    dig AAAA yourdomain.com
    dig MX yourdomain.com
    dig TXT yourdomain.com
    dig CNAME www.yourdomain.com
    

    Compare the answers against the values in the original Cloudflare zone to confirm the migration is complete.

    Keep the Cloudflare DNS zone intact for at least 48 hours after migration. Switching the nameservers back at the registrar provides a rollback path if the CDB configuration needs to be corrected — the rollback is subject to the same DNS propagation delay.

Create a CDB DNS zone and import records from a BIND zone file as the CDB-side steps of a Cloudflare-to-CDB migration. Export the zone file from Cloudflare first, and update nameservers at the domain registrar after the import completes. To update nameservers, set them to ns1.gcorelabs.net and ns2.gcdn.services in the registrar's control panel.

Before importing the zone file, review records that were proxied through Cloudflare and confirm their values point to the intended origin server or Orange's CDN endpoint.

An API token is required.

Open a terminal and export these variables:

export GCORE_API_KEY="{YOUR_API_KEY}"
export ZONE_NAME="{YOUR_ZONE_NAME}"
export ZONE_FILE="{PATH_TO_ZONE_FILE}"

Quickstart

Create a zone and import a BIND zone file with a single script.

import os
from gcore import CDB

ZONE_NAME = os.environ["ZONE_NAME"]
ZONE_FILE  = os.environ["ZONE_FILE"]

client = CDB()

# Step 1. Create the DNS zone
client.dns.zones.create(name=ZONE_NAME)
print(f"Zone created: {ZONE_NAME}")

# Step 2. Import DNS records
with open(ZONE_FILE, "rb") as f:
    zone_data = f.read()

result = client.dns.zones.import_(
    zone_name=ZONE_NAME,
    body=zone_data,
    extra_headers={"Content-Type": "text/plain"},
)
print(f"Imported: {result.imported.rrsets} RRsets, {result.imported.resource_records} records")
package main

import (
    "bytes"
    "context"
    "fmt"
    "io"
    "net/http"
    "os"

    gcore "github.com/G-Core/gcore-go"
    "github.com/G-Core/gcore-go/dns"
)

func main() {
    client := gcore.NewClient()
    ctx := context.Background()

    zoneName     := os.Getenv("ZONE_NAME")
    zoneFilePath := os.Getenv("ZONE_FILE")

    // Step 1. Create the DNS zone
    _, err := client.DNS.Zones.New(ctx, dns.ZoneNewParams{Name: zoneName})
    if err != nil {
        panic(err)
    }
    fmt.Printf("Zone created: %s\n", zoneName)

    // Step 2. Import DNS records
    zoneData, err := os.ReadFile(zoneFilePath)
    if err != nil {
        panic(err)
    }

    req, err := http.NewRequest("POST",
        "https://api.controlcenter.internationalcarriers.orange.com/dns/v2/zones/"+zoneName+"/import",
        bytes.NewReader(zoneData))
    if err != nil {
        panic(err)
    }
    req.Header.Set("Authorization", "APIKey "+os.Getenv("GCORE_API_KEY"))
    req.Header.Set("Content-Type", "text/plain")

    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()
    body, _ := io.ReadAll(resp.Body)
    fmt.Printf("Import response: %s\n", string(body))
}

Create the zone and import records

  1. 1

    Create the DNS zone

    Create the destination zone. The request requires only the domain name.

    ParameterRequiredDescription
    nameYesThe domain name to create the zone for, e.g. yourdomain.com.
    import os
    from gcore import CDB
    
    client = CDB()
    client.dns.zones.create(name=os.environ["ZONE_NAME"])
    print(f"Zone created: {os.environ['ZONE_NAME']}")
    
    package main
    
    import (
        "context"
        "fmt"
        "os"
    
        gcore "github.com/G-Core/gcore-go"
        "github.com/G-Core/gcore-go/dns"
    )
    
    func main() {
        client := gcore.NewClient()
        ctx := context.Background()
    
        _, err := client.DNS.Zones.New(ctx, dns.ZoneNewParams{Name: os.Getenv("ZONE_NAME")})
        if err != nil {
            panic(err)
        }
        fmt.Printf("Zone created: %s\n", os.Getenv("ZONE_NAME"))
    }
    
    curl -s -X POST "https://api.controlcenter.internationalcarriers.orange.com/dns/v2/zones" \
      -H "Authorization: APIKey $GCORE_API_KEY" \
      -H "Content-Type: application/json" \
      -d "{\"name\": \"${ZONE_NAME}\"}"
    

    The API returns:

    {"id": 12345}
    
  2. 2

    Import DNS records

    Send the BIND zone file as a plain-text request body. Import adds new RRsets to the zone; existing RRsets with the same name and type are skipped. The warnings field in the response lists any skipped records.

    import os
    from gcore import CDB
    
    client = CDB()
    
    with open(os.environ["ZONE_FILE"], "rb") as f:
        zone_data = f.read()
    
    result = client.dns.zones.import_(
        zone_name=os.environ["ZONE_NAME"],
        body=zone_data,
        extra_headers={"Content-Type": "text/plain"},
    )
    print(f"Imported: {result.imported.rrsets} RRsets, {result.imported.resource_records} records")
    
    package main
    
    import (
        "bytes"
        "fmt"
        "io"
        "net/http"
        "os"
    )
    
    func main() {
        zoneName := os.Getenv("ZONE_NAME")
    
        zoneData, err := os.ReadFile(os.Getenv("ZONE_FILE"))
        if err != nil {
            panic(err)
        }
    
        req, err := http.NewRequest("POST",
            "https://api.controlcenter.internationalcarriers.orange.com/dns/v2/zones/"+zoneName+"/import",
            bytes.NewReader(zoneData))
        if err != nil {
            panic(err)
        }
        req.Header.Set("Authorization", "APIKey "+os.Getenv("GCORE_API_KEY"))
        req.Header.Set("Content-Type", "text/plain")
    
        resp, err := http.DefaultClient.Do(req)
        if err != nil {
            panic(err)
        }
        defer resp.Body.Close()
        body, _ := io.ReadAll(resp.Body)
        fmt.Printf("Status: %d\n%s\n", resp.StatusCode, string(body))
    }
    
    curl -s -X POST "https://api.controlcenter.internationalcarriers.orange.com/dns/v2/zones/${ZONE_NAME}/import" \
      -H "Authorization: APIKey $GCORE_API_KEY" \
      -H "Content-Type: text/plain" \
      --data-binary "@$ZONE_FILE"
    

    The API returns:

    {
      "imported": {
        "rrsets": 5,
        "resource_records": 5,
        "qtype": 4,
        "skipped_resource_records": 0
      },
      "success": true,
      "warnings": {}
    }
    
  3. 3

    Verify imported records

    List all RRsets in the zone to confirm the records were created.

    import os
    from gcore import CDB
    
    client = CDB()
    page = client.dns.zones.rrsets.list(zone_name=os.environ["ZONE_NAME"])
    print(f"Total RRsets: {page.total_amount}")
    for rr in page.rrsets or []:
        print(f"  {rr.type:<6}  {rr.name}")
    
    package main
    
    import (
        "context"
        "fmt"
        "os"
    
        gcore "github.com/G-Core/gcore-go"
        "github.com/G-Core/gcore-go/dns"
    )
    
    func main() {
        client := gcore.NewClient()
        ctx := context.Background()
    
        page, err := client.DNS.Zones.Rrsets.List(ctx, os.Getenv("ZONE_NAME"), dns.ZoneRrsetListParams{})
        if err != nil {
            panic(err)
        }
        fmt.Printf("Total RRsets: %d\n", page.TotalAmount)
        for _, rr := range page.Rrsets {
            fmt.Printf("  %-6s  %s\n", rr.Type, rr.Name)
        }
    }
    
    curl -s "https://api.controlcenter.internationalcarriers.orange.com/dns/v2/zones/${ZONE_NAME}/rrsets" \
      -H "Authorization: APIKey $GCORE_API_KEY"
    

    The API returns:

    {
      "rrsets": [
        {"name": "example.com", "type": "A",     "ttl": 300,  "resource_records": [&#123;"content": ["203.0.113.10"]&#125;]},
        {"name": "example.com", "type": "MX",    "ttl": 300,  "resource_records": [&#123;"content": [10, "mail.example.com."]&#125;]},
        {"name": "example.com", "type": "NS",    "ttl": 3600, "resource_records": [&#123;"content": ["ns1.gcorelabs.net."]&#125;]},
        {"name": "example.com", "type": "TXT",   "ttl": 300,  "resource_records": [&#123;"content": ["v=spf1 mx -all"]&#125;]},
        {"name": "mail.example.com", "type": "A",     "ttl": 300, "resource_records": [&#123;"content": ["203.0.113.11"]&#125;]},
        {"name": "www.example.com",  "type": "CNAME", "ttl": 300, "resource_records": [&#123;"content": ["example.com."]&#125;]}
      ],
      "total_amount": 6
    }
    

After confirming the imported records are correct, update the nameservers at the domain registrar to ns1.gcorelabs.net and ns2.gcdn.services. Nameserver changes can take up to 24 hours to propagate globally.