Skip to content

Instantly share code, notes, and snippets.

@neuhaus
Last active August 29, 2025 17:36
Show Gist options
  • Select an option

  • Save neuhaus/9f26a85f426d32e9d05c6db9eadc2db2 to your computer and use it in GitHub Desktop.

Select an option

Save neuhaus/9f26a85f426d32e9d05c6db9eadc2db2 to your computer and use it in GitHub Desktop.
retrieve all domain zones from IONOS using the API and convert them into bind zone files.
#!/bin/bash
# Check if API key is provided
if [ -z "$IONOS_API_KEY" ]; then
echo "Error: IONOS_API_KEY environment variable must be set"
exit 1
fi
# Base API URL
API_BASE="https://api.hosting.ionos.com/dns/v1"
# Function to convert records to BIND zone format
convert_zone_to_bind() {
local zone_name="$1"
local records="$2"
local output_file="${zone_name}.zone"
# Generate zone file header
cat > "$output_file" << EOL
\$ORIGIN ${zone_name}.
\$TTL 3600
@ IN SOA ns1.${zone_name}. hostmaster.${zone_name}. (
$(date +%Y%m%d%H) ; Serial
3600 ; Refresh
1800 ; Retry
604800 ; Expire
86400 ) ; Minimum TTL
; Nameservers
@ IN NS ns1.${zone_name}.
@ IN NS ns2.${zone_name}.
EOL
# Process each record
echo "$records" | jq -c '.[]' | while read -r record; do
local name=$(echo "$record" | jq -r '.name')
local type=$(echo "$record" | jq -r '.type')
local content=$(echo "$record" | jq -r '.content')
local ttl=$(echo "$record" | jq -r '.ttl // 3600')
local prio=$(echo "$record" | jq -r '.prio // ""')
# Strip zone name from the record name if present
name="${name%%.$zone_name}"
# Convert record types to BIND format
case "$type" in
A)
echo "${name:-@} IN A $content" >> "$output_file"
;;
AAAA)
echo "${name:-@} IN AAAA $content" >> "$output_file"
;;
CNAME)
echo "${name:-@} IN CNAME $content." >> "$output_file"
;;
MX)
echo "${name:-@} IN MX $prio $content." >> "$output_file"
;;
NS)
echo "${name:-@} IN NS $content." >> "$output_file"
;;
TXT)
echo "${name:-@} IN TXT $content" >> "$output_file"
;;
SRV)
# SRV record format: _service._protocol.name TTL class SRV priority weight port target
echo "$name IN SRV $prio 1 $(echo "$content" | cut -d' ' -f2-)" >> "$output_file"
;;
CAA)
echo "${name:-@} IN CAA $content" >> "$output_file"
;;
esac
done
echo "Generated zone file for $zone_name: $output_file"
}
# Retrieve list of zones
zones=$(curl -s -X GET "$API_BASE/zones" \
-H "X-API-Key: $IONOS_API_KEY" \
-H "Accept: application/json")
# Process each zone
echo "$zones" | jq -c '.[]' | while read -r zone; do
zone_id=$(echo "$zone" | jq -r '.id')
zone_name=$(echo "$zone" | jq -r '.name')
# Retrieve zone details with all records
zone_details=$(curl -s -X GET "$API_BASE/zones/$zone_id" \
-H "X-API-Key: $IONOS_API_KEY" \
-H "Accept: application/json")
# Extract and convert records
records=$(echo "$zone_details" | jq '.records')
convert_zone_to_bind "$zone_name" "$records"
done
@neuhaus
Copy link
Author

neuhaus commented Dec 18, 2024

claude.ai prompt:

"Using the openapi spec file, convert DNS zones retrieved using this API into bind zone files with a shell script using jq"

then paste the openapi spec file from https://developer.hosting.ionos.de/assets/kms-swagger-specs/dns.yaml

only thing to manually fix so far has been the double quoted TXT records.

@CraigWilson-GMCA
Copy link

Overall, this script is great and is saving me a goo amount of time in migrating our IONOS records to Cloudflare

One issue I have had that yo may need to look at is that when you use the resultant zone files to import the records, it is appending the domain suffix to all the records - we've specifically seen it on CNAME and MX records and it caused the site to go offline after period of time

So the IONOS MX record would end up looking like this:
Screenshot 2025-08-29 090433

I believe this is because the exported BIND files are missing the "." right at the end of the record name

The exported file shows
ourdomain.org IN MX 10 mx00.ionos.co.uk

and not
ourdomain.org IN MX 10 mx00.ionos.co.uk.

Once we edited one of the files to use the "Dotted FQDN" format, the records imported without issue

@neuhaus
Copy link
Author

neuhaus commented Aug 29, 2025

Thanks for the feedback. While i haven't had this issue, i've added the trailing dot to NS, CNAME and NS records in the script. SRV records are a bit more tricky so i leave that as an exercise for the reader.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment