
How to export and back up your DNS zone file?
To export and back up your DNS zone file, use your DNS provider's export feature, either the "Export" button in the dashboard or the equivalent API or CLI command, to download all records in standard BIND zone file format. Save that file somewhere outside your DNS provider, ideally in a version-controlled repository, and automate the export on a schedule so you always have a recent copy. If you run your own nameservers, the zone file already exists on disk, and you can back it up directly or pull a copy with a zone transfer.
A DNS backup is one of those things nobody thinks about until a record is deleted by mistake, an account is compromised, or a provider has a bad day. Without one, you're left rebuilding records from memory and old emails. This guide shows you how to export zones from the most common providers, how to check the export is complete, how to automate versioned backups, and how to restore from them.
What a Zone File Is
A zone file is a plain-text file that lists every DNS record in a zone. The format was defined for BIND decades ago and is still the common language most providers can read and write. A small one looks like this:
$ORIGIN example.com.
$TTL 3600
@ IN SOA ns1.example.com. hostmaster.example.com. (
2026101601 3600 900 1209600 300 )
@ IN NS ns1.example.com.
@ IN NS ns2.example.com.
@ IN A 192.0.2.10
@ IN AAAA 2001:db8::10
www IN CNAME example.com.
@ IN MX 10 mail.example.com.
mail IN A 192.0.2.25
@ IN TXT "v=spf1 mx -all"
_dmarc IN TXT "v=DMARC1; p=reject; rua=mailto:dmarc@example.com"
@ IN CAA 0 issue "letsencrypt.org"
Because it's plain text, a zone file is easy to store, compare and review. That's what makes it such a good backup format.
Why You Should Back Up DNS
DNS providers are reliable, but backups protect you from things that aren't the provider's fault:
- Human error: Someone deletes the wrong record or overwrites a zone with a bulk import.
- Account compromise: An attacker changes or removes records, and you need to know what they looked like before.
- Automation bugs: A Terraform plan or script removes records it doesn't know about.
- Provider changes: You lose access to an account, a billing problem suspends a zone, or you need to move providers in a hurry.
- Auditing: A history of exports shows when each record changed, which helps when debugging email or certificate problems.
Exporting From Common Providers
Cloudflare
In the dashboard, open your domain, go to DNS > Records, and use Import and Export to download the zone as a BIND file. The API equivalent is:
curl -s "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records/export" \
-H "Authorization: Bearer $CF_API_TOKEN" \
-o example.com.zone
The API token needs Zone: DNS: Read permission for the zone. You can find the zone ID on the domain's overview page or with:
curl -s "https://api.cloudflare.com/client/v4/zones?name=example.com" \
-H "Authorization: Bearer $CF_API_TOKEN" | jq -r '.result[0].id'
The exported file includes comments noting which records are proxied, which is useful context when you restore.
Amazon Route 53
Route 53 doesn't offer a BIND export in the console. The AWS CLI gives you every record as JSON:
aws route53 list-resource-record-sets \
--hosted-zone-id Z0123456789ABCDEFGHIJ \
--output json > example.com.route53.json
The CLI handles pagination automatically, so large zones come back complete. JSON is a perfectly good backup because it can be fed back into change-resource-record-sets later. If you want BIND format too, the open-source tool cli53 can export it:
cli53 export --full example.com > example.com.zone
Alternatively, you can convert the records yourself with a short Python script using boto3:
#!/usr/bin/env python3
"""Export a Route 53 hosted zone to BIND-style text."""
import sys
import boto3
zone_id = sys.argv[1]
client = boto3.client("route53")
paginator = client.get_paginator("list_resource_record_sets")
for page in paginator.paginate(HostedZoneId=zone_id):
for rrset in page["ResourceRecordSets"]:
name, rtype = rrset["Name"], rrset["Type"]
if "AliasTarget" in rrset:
target = rrset["AliasTarget"]["DNSName"]
print(f"; ALIAS {name} {rtype} -> {target}")
continue
ttl = rrset.get("TTL", 300)
for record in rrset.get("ResourceRecords", []):
print(f"{name}\t{ttl}\tIN\t{rtype}\t{record['Value']}")
python3 route53_to_bind.py Z0123456789ABCDEFGHIJ > example.com.zone
Alias records have no equivalent in standard zone files, so the script writes them as comments. Weighted, latency-based and failover records also export as several entries with the same name, and their routing settings live only in the JSON, which is another reason to keep the JSON copy.
Google Cloud DNS
The gcloud CLI can export directly in zone file format:
gcloud dns record-sets export example.com.zone \
--zone=example-com \
--zone-file-format
Here example-com is the name of the managed zone in Google Cloud, not the domain name. List your zones with gcloud dns managed-zones list if you're not sure.
Azure DNS
The Azure CLI exports a zone file for any public DNS zone:
az network dns zone export \
--resource-group my-resource-group \
--name example.com \
--file-name example.com.zone
DigitalOcean
DigitalOcean's control panel and doctl list records rather than exporting a BIND file, so save them as JSON:
doctl compute domain records list example.com --output json > example.com.do.json
Registrar DNS and Smaller Providers
Many registrars and hosting companies have an "Export zone" or "Download zone file" option somewhere in the DNS management page. If there isn't one, check whether they offer an API. As a last resort, copy the records manually from the dashboard into a text file. It's tedious, but it's far better than having nothing.
Exporting From Your Own Nameservers
If you run BIND, Knot, NSD or PowerDNS, your records are already in files or a database on the server.
Back Up the Files Directly
For BIND, back up the zone files and the configuration that defines them:
sudo tar czf dns-backup-$(date +%F).tar.gz \
/etc/bind/named.conf* /etc/bind/zones/
If the zone is DNSSEC-signed with inline signing, BIND keeps the signed version in separate .signed and journal files. Back up the unsigned source zone and the key files in your key directory. Without the keys, you'd need to generate new ones and update the DS record at your registrar.
Use a Zone Transfer
A zone transfer (AXFR) asks a nameserver for the whole zone. It's the most accurate way to get what a server is really serving, including dynamic updates that may not be in the file yet:
dig @ns1.example.com example.com AXFR +nocomments +nostats > example.com.axfr
Zone transfers should be restricted to trusted IPs, so allow your backup host in named.conf rather than opening it to everyone:
zone "example.com" {
type primary;
file "/etc/bind/zones/db.example.com";
allow-transfer { 192.0.2.53; 198.51.100.53; };
};
For a file that's easier to read and compare, BIND's named-compilezone can normalise it, writing each record on one line with fully qualified names:
named-compilezone -o example.com.normalised example.com example.com.axfr
Check That the Export Is Complete
An export is only useful if it's correct. After your first export from any provider, check it:
- Count records: Compare the number of records in the file with the count shown in the dashboard.
- Look for important names: Search the file for your DKIM selectors,
_dmarc, MX records and verification TXT records. - Validate syntax: Run
named-checkzoneagainst BIND-format files.
named-checkzone example.com example.com.zone
zone example.com/IN: loaded serial 2026101601
OK
- Spot-check against live DNS: Pick a few records from the file and confirm them with
digagainst your authoritative nameserver.
dig @ns1.example.com _dmarc.example.com TXT +short
"v=DMARC1; p=reject; rua=mailto:dmarc@example.com"
Remember that features which aren't really DNS records, such as registrar URL forwarding or email forwarding, never appear in an export. Document those separately.
Automating Versioned Backups
A one-off export goes out of date the next time anyone edits a record. The best approach is a scheduled job that exports every zone and commits the result to a Git repository. Git gives you a full history, and git diff shows exactly what changed between backups.
Here's a simple script for Cloudflare zones:
#!/usr/bin/env bash
# backup-dns.sh - export all Cloudflare zones into a git repository
set -euo pipefail
BACKUP_DIR="$HOME/dns-backups"
cd "$BACKUP_DIR"
zones=$(curl -s "https://api.cloudflare.com/client/v4/zones?per_page=50" \
-H "Authorization: Bearer $CF_API_TOKEN" | jq -r '.result[] | "\(.id) \(.name)"')
while read -r id name; do
curl -s "https://api.cloudflare.com/client/v4/zones/$id/dns_records/export" \
-H "Authorization: Bearer $CF_API_TOKEN" \
| grep -v '^;; Exported' > "$name.zone"
done <<< "$zones"
git add -A
if ! git diff --cached --quiet; then
git commit -m "DNS backup $(date -u +%Y-%m-%dT%H:%MZ)"
fi
The grep -v line removes the export timestamp comment, so a commit only happens when records have actually changed. If you have more than 50 zones, you'll need to loop over the API's pages as well.
Run it from cron every few hours:
0 */6 * * * CF_API_TOKEN=xxxxxxxx /home/backup/backup-dns.sh >> /var/log/dns-backup.log 2>&1
In practice, keep the token in a file readable only by the backup user, or in a secrets manager, rather than in the crontab itself. Give the token read-only DNS permissions so a leaked backup credential can't change anything.
What Else to Store With the Zone
A complete DNS backup is more than records. Keep a note alongside the zone files with:
- Nameservers assigned to each domain, and which registrar each domain is with.
- DNSSEC details: The DS record published at the registrar, and whether keys are managed by the provider.
- Registrar features: URL forwarding rules, email forwarding and any glue records for custom nameservers.
- Provider-only settings: Proxy status, health checks, load balancing pools and routing policies that a plain zone file can't express.
Restoring From a Backup
How you restore depends on what went wrong.
Restoring a Single Record
For one deleted or changed record, find it in your backup and recreate it in the dashboard. With Git, you can see exactly what it used to be:
git log -p -- example.com.zone | grep -n "_dmarc"
Restoring a Whole Zone
Most providers can import a BIND zone file. With Cloudflare's API:
curl -s -X POST \
"https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records/import" \
-H "Authorization: Bearer $CF_API_TOKEN" \
-F "file=@example.com.zone"
Import usually adds records rather than replacing the zone, and records that already exist may be skipped or cause errors. Check the result carefully, and remove any leftover incorrect records by hand.
For Route 53, convert your JSON backup into a change batch with UPSERT actions and submit it with aws route53 change-resource-record-sets. Leave out the SOA and the apex NS records, because Route 53 manages those for the hosted zone.
For BIND, copy the zone file back into place, increment the SOA serial so secondaries notice, check it and reload:
sudo named-checkzone example.com /etc/bind/zones/db.example.com
sudo rndc reload example.com
Test Your Restore
A backup you've never restored is a backup you hope works. Once in a while, import a backup into a test zone, such as a spare domain or a zone created at a second provider that isn't delegated, and query it directly with dig @ to confirm the records came through intact.
Comparison of Export Methods
| Provider | Dashboard export | CLI or API export | Format |
|---|---|---|---|
| Cloudflare | Yes | API dns_records/export | BIND |
| Amazon Route 53 | No | aws route53 list-resource-record-sets | JSON |
| Google Cloud DNS | No | gcloud dns record-sets export | BIND or YAML |
| Azure DNS | No | az network dns zone export | BIND |
| DigitalOcean | No | doctl compute domain records list | JSON |
| Self-hosted BIND | Not applicable | Zone files or AXFR | BIND |
FAQ: Exporting and Backing Up DNS
Most providers export in BIND zone file format, a plain-text format where each line is a record with a name, TTL, class, type and value. Some, such as Route 53 and DigitalOcean, return JSON through their CLI or API instead.
Not normally. Public queries only return records you ask for by name, and zone transfers should be restricted to authorised servers. You can export zones only from accounts and servers you control.
Automated daily or six-hourly exports work well for most organisations. Committing them to Git means unchanged zones don't create new versions, so frequent backups cost almost nothing.
No. Managed providers keep private signing keys to themselves, and exports usually leave out signatures. If you restore to a different provider, you'll need to enable DNSSEC there and publish a new DS record.
URL forwarding isn't a DNS record. It's a web service run by your registrar or DNS provider that answers on an IP address in your zone. Document those rules separately so you can recreate them.
Usually, yes. BIND format is widely supported for imports. Remove the SOA and NS records first, and check for provider-specific records or comments that the new provider might not understand.
Conclusion
Exporting your DNS zone takes a minute, and it can save hours of guesswork after a mistake or security incident. Every major provider offers a way to get your records out, whether that's a BIND file from Cloudflare, Google Cloud or Azure, JSON from Route 53 and DigitalOcean, or the zone files and AXFR on your own servers.
Turn that export into a habit by automating it, keeping the results in Git, and storing the details a zone file can't capture, such as DNSSEC settings and registrar forwarding. Test a restore once in a while, and you'll have a reliable record of your DNS that you can fall back on whenever you need it.


