Cloudflare API ile Dinamik DNS (DDNS) Güncelleme
Evde bir sunucu çalıştırıyorsanız, küçük bir VPS’iniz var ama statik IP için para vermek istemiyorsanız ya da dinamik IP’li bir internet bağlantısında servis açmak zorundaysanız, bu yazı tam size göre. ISS’ler (İnternet Servis Sağlayıcısı) çoğunlukla dinamik IP dağıtır, yani modem yeniden başladığında veya bağlantı kesilip geldiğinde IP adresiniz değişebilir. Bu durumda alan adınızı sürekli güncel tutmak için Cloudflare API’yi kullanarak kendi DDNS sisteminizi kurabilirsiniz. Hem ücretsiz, hem güvenilir, hem de tam kontrol sizde.
Cloudflare DDNS Neden Mantıklı?
Piyasada No-IP, DynDNS gibi çözümler var ama bunların ücretsiz planları kısıtlı veya her 30 günde bir manuel onay istiyor. Cloudflare’in DNS yönetim API’si ise tamamen ücretsiz, rate limit’leri oldukça cömert ve zaten alan adınızı Cloudflare’de yönetiyorsanız ekstra bir servis gerekmez.
Cloudflare API v4 ile bir DNS kaydını güncellemek için tek ihtiyacınız:
- Cloudflare hesabı ve API token’ı
- Alan adınızın Cloudflare’de olması
- Bir bash script veya Python scripti
- Cron job veya systemd timer
Hepsi bu kadar. Şimdi adım adım kuralım.
Cloudflare API Token Oluşturma
Eski yöntem Global API Key kullanmaktı ama bu güvenlik açısından kötü bir pratik. Tüm hesabınıza erişim veriyor. Bunun yerine scoped token oluşturun, sadece ihtiyacınız olan izinleri verin.
Cloudflare dashboard’a girin:
- Sağ üst köşeden profil ikonuna tıklayın
- “My Profile” > “API Tokens” > “Create Token”
- “Edit zone DNS” şablonunu seçin
- Zone Resources kısmında sadece DDNS yapacağınız alan adını seçin
- Token’ı oluşturun ve kaydedin, bir daha göremezsiniz
Token’ınızı test edin:
curl -X GET "https://api.cloudflare.com/client/v4/user/tokens/verify"
-H "Authorization: Bearer YOUR_API_TOKEN"
-H "Content-Type: application/json"
Başarılı yanıt şöyle görünmeli:
{
"result": {
"id": "...",
"status": "active"
},
"success": true
}
Zone ID ve Record ID Bulma
Script yazarken iki bilgiye ihtiyacınız var: Zone ID ve güncellenecek DNS kaydının Record ID’si.
Zone ID bulmak:
curl -X GET "https://api.cloudflare.com/client/v4/zones?name=ornekdomain.com"
-H "Authorization: Bearer YOUR_API_TOKEN"
-H "Content-Type: application/json" | python3 -m json.tool
Çıktıdan id alanını alın, bu sizin Zone ID’niz. Cloudflare dashboard’da da alan adınızın sağ tarafında “Zone ID” olarak görünür.
DNS Record ID bulmak:
curl -X GET "https://api.cloudflare.com/client/v4/zones/ZONE_ID/dns_records?type=A&name=home.ornekdomain.com"
-H "Authorization: Bearer YOUR_API_TOKEN"
-H "Content-Type: application/json" | python3 -m json.tool
Bu sorgu home.ornekdomain.com için A kaydının ID’sini döner. Eğer kayıt yoksa önce manuel olarak Cloudflare dashboard’dan oluşturun, herhangi bir IP yazın, script zaten güncelleyecek.
Temel Bash Script
En sade haliyle çalışan bir DDNS update scripti:
#!/bin/bash
# Cloudflare DDNS Updater
# /usr/local/bin/cloudflare-ddns.sh
CF_API_TOKEN="your_api_token_here"
CF_ZONE_ID="your_zone_id_here"
CF_RECORD_ID="your_record_id_here"
CF_RECORD_NAME="home.ornekdomain.com"
CF_PROXIED=false
CF_TTL=120
# Mevcut public IP'yi al
CURRENT_IP=$(curl -s https://api.ipify.org)
if [ -z "$CURRENT_IP" ]; then
echo "$(date): IP adresi alinamadi!" >&2
exit 1
fi
# Son kaydedilen IP ile karsilastir
LAST_IP_FILE="/tmp/cloudflare_last_ip.txt"
if [ -f "$LAST_IP_FILE" ]; then
LAST_IP=$(cat "$LAST_IP_FILE")
if [ "$CURRENT_IP" = "$LAST_IP" ]; then
echo "$(date): IP degismedi ($CURRENT_IP), guncelleme gerekmiyor."
exit 0
fi
fi
# Cloudflare DNS kaydini guncelle
RESPONSE=$(curl -s -X PUT
"https://api.cloudflare.com/client/v4/zones/${CF_ZONE_ID}/dns_records/${CF_RECORD_ID}"
-H "Authorization: Bearer ${CF_API_TOKEN}"
-H "Content-Type: application/json"
--data "{"type":"A","name":"${CF_RECORD_NAME}","content":"${CURRENT_IP}","ttl":${CF_TTL},"proxied":${CF_PROXIED}}")
SUCCESS=$(echo "$RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin)['success'])")
if [ "$SUCCESS" = "True" ]; then
echo "$CURRENT_IP" > "$LAST_IP_FILE"
echo "$(date): DNS guncellendi: ${CF_RECORD_NAME} -> ${CURRENT_IP}"
else
echo "$(date): Guncelleme basarisiz! Yanit: $RESPONSE" >&2
exit 1
fi
Script’i çalıştırılabilir yapın:
chmod +x /usr/local/bin/cloudflare-ddns.sh
Gelişmiş Script: Çoklu Subdomain ve Loglama
Gerçek dünyada birden fazla subdomain güncellemek gerekebilir. Homelab’da nas.ornekdomain.com, vpn.ornekdomain.com, git.ornekdomain.com gibi kayıtların hepsini tek IP’ye yönlendiriyorsunuz ve hepsini güncellemek istiyorsunuz.
#!/bin/bash
# Cloudflare Multi-Record DDNS Updater
# Versiyon: 2.0
CF_API_TOKEN="your_api_token_here"
CF_ZONE_ID="your_zone_id_here"
LOG_FILE="/var/log/cloudflare-ddns.log"
IP_CACHE_FILE="/var/cache/cloudflare-ddns/last_ip"
# Guncellenecek kayitlar: "record_id:record_name" formati
RECORDS=(
"abc123def456:home.ornekdomain.com"
"xyz789uvw012:nas.ornekdomain.com"
"qrs345tuv678:vpn.ornekdomain.com"
)
log() {
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE"
}
# Cache dizini olustur
mkdir -p "$(dirname "$IP_CACHE_FILE")"
# Public IP al, birden fazla servis dene
get_public_ip() {
local ip
for service in "https://api.ipify.org" "https://icanhazip.com" "https://ifconfig.me"; do
ip=$(curl -s --max-time 10 "$service" 2>/dev/null | tr -d '[:space:]')
if echo "$ip" | grep -qE '^[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}$'; then
echo "$ip"
return 0
fi
done
return 1
}
CURRENT_IP=$(get_public_ip)
if [ $? -ne 0 ] || [ -z "$CURRENT_IP" ]; then
log "HATA: Public IP alinamadi, tum servisler yanit vermedi."
exit 1
fi
# IP degismemisse cikis yap
if [ -f "$IP_CACHE_FILE" ] && [ "$(cat "$IP_CACHE_FILE")" = "$CURRENT_IP" ]; then
log "IP degismedi: $CURRENT_IP"
exit 0
fi
log "Yeni IP tespit edildi: $CURRENT_IP"
ERRORS=0
for record in "${RECORDS[@]}"; do
RECORD_ID="${record%%:*}"
RECORD_NAME="${record##*:}"
RESPONSE=$(curl -s -X PUT
"https://api.cloudflare.com/client/v4/zones/${CF_ZONE_ID}/dns_records/${RECORD_ID}"
-H "Authorization: Bearer ${CF_API_TOKEN}"
-H "Content-Type: application/json"
--data "{"type":"A","name":"${RECORD_NAME}","content":"${CURRENT_IP}","ttl":120,"proxied":false}")
if echo "$RESPONSE" | grep -q '"success":true'; then
log "OK: ${RECORD_NAME} -> ${CURRENT_IP}"
else
log "HATA: ${RECORD_NAME} guncellenemedi. Yanit: $RESPONSE"
ERRORS=$((ERRORS + 1))
fi
done
if [ $ERRORS -eq 0 ]; then
echo "$CURRENT_IP" > "$IP_CACHE_FILE"
log "Tum kayitlar basariyla guncellendi."
else
log "UYARI: $ERRORS kayit guncellenemedi."
exit 1
fi
Python Versiyonu
Bash yerine Python tercih ediyorsanız, daha okunabilir ve hata yönetimi güçlü bir versiyon:
#!/usr/bin/env python3
# /usr/local/bin/cloudflare-ddns.py
import requests
import json
import sys
import os
import logging
from datetime import datetime
# Yapilandirma
CF_API_TOKEN = os.environ.get("CF_API_TOKEN", "your_token_here")
CF_ZONE_ID = os.environ.get("CF_ZONE_ID", "your_zone_id_here")
CF_RECORD_ID = os.environ.get("CF_RECORD_ID", "your_record_id_here")
CF_RECORD_NAME = os.environ.get("CF_RECORD_NAME", "home.ornekdomain.com")
IP_CACHE_FILE = "/var/cache/cloudflare-ddns/last_ip"
LOG_FILE = "/var/log/cloudflare-ddns.log"
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s",
handlers=[
logging.FileHandler(LOG_FILE),
logging.StreamHandler(sys.stdout)
]
)
logger = logging.getLogger(__name__)
def get_public_ip():
services = [
"https://api.ipify.org",
"https://icanhazip.com",
"https://api4.my-ip.io/ip"
]
for service in services:
try:
response = requests.get(service, timeout=10)
ip = response.text.strip()
# Basit IPv4 dogrulama
parts = ip.split(".")
if len(parts) == 4 and all(p.isdigit() and 0 <= int(p) <= 255 for p in parts):
return ip
except Exception as e:
logger.warning(f"IP servisi basarisiz ({service}): {e}")
return None
def update_dns_record(ip):
headers = {
"Authorization": f"Bearer {CF_API_TOKEN}",
"Content-Type": "application/json"
}
data = {
"type": "A",
"name": CF_RECORD_NAME,
"content": ip,
"ttl": 120,
"proxied": False
}
url = f"https://api.cloudflare.com/client/v4/zones/{CF_ZONE_ID}/dns_records/{CF_RECORD_ID}"
response = requests.put(url, headers=headers, json=data, timeout=15)
return response.json()
def main():
os.makedirs(os.path.dirname(IP_CACHE_FILE), exist_ok=True)
current_ip = get_public_ip()
if not current_ip:
logger.error("Public IP alinamadi!")
sys.exit(1)
# Cache kontrolu
if os.path.exists(IP_CACHE_FILE):
with open(IP_CACHE_FILE, "r") as f:
last_ip = f.read().strip()
if last_ip == current_ip:
logger.info(f"IP degismedi: {current_ip}")
sys.exit(0)
logger.info(f"Yeni IP: {current_ip}, DNS guncelleniyor...")
result = update_dns_record(current_ip)
if result.get("success"):
with open(IP_CACHE_FILE, "w") as f:
f.write(current_ip)
logger.info(f"Basarili: {CF_RECORD_NAME} -> {current_ip}")
else:
errors = result.get("errors", [])
logger.error(f"Guncelleme basarisiz: {errors}")
sys.exit(1)
if __name__ == "__main__":
main()
Bu scripti çalıştırmadan önce requests kütüphanesini kurun:
pip3 install requests
# veya sistem paketi olarak:
apt install python3-requests # Debian/Ubuntu
dnf install python3-requests # RHEL/Fedora
Ortam Değişkenleriyle Güvenli Yapılandırma
API token’ı script içine yazmak iyi bir pratik değil. Bunun yerine ortam değişkenleri veya ayrı bir config dosyası kullanın.
Config dosyası yöntemi:
# /etc/cloudflare-ddns.conf
# Bu dosyanın izinlerini kisitlayin!
CF_API_TOKEN="gercek_token_buraya"
CF_ZONE_ID="gercek_zone_id"
CF_RECORD_ID="gercek_record_id"
CF_RECORD_NAME="home.ornekdomain.com"
Dosya izinlerini ayarlayın:
chmod 600 /etc/cloudflare-ddns.conf
chown root:root /etc/cloudflare-ddns.conf
Script’in başında bu dosyayı source edin:
#!/bin/bash
source /etc/cloudflare-ddns.conf
# veya
[ -f /etc/cloudflare-ddns.conf ] && . /etc/cloudflare-ddns.conf
Cron Job ile Otomatikleştirme
Her 5 dakikada bir IP kontrolü yapmak için crontab’a ekleyin:
crontab -e
# Her 5 dakikada Cloudflare DDNS guncelle
*/5 * * * * /usr/local/bin/cloudflare-ddns.sh >> /var/log/cloudflare-ddns.log 2>&1
# Alternatif: root olarak calistirmak icin /etc/cron.d/cloudflare-ddns dosyasi
*/5 * * * * root /usr/local/bin/cloudflare-ddns.sh
Systemd Timer ile Otomatikleştirme
Cron yerine systemd timer kullanmak isteyenler için daha modern bir yaklaşım. Systemd, servis başarısız olduğunda daha iyi loglama ve bağımlılık yönetimi sunar.
Service dosyası oluşturun:
# /etc/systemd/system/cloudflare-ddns.service
[Unit]
Description=Cloudflare DDNS Updater
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/cloudflare-ddns.sh
EnvironmentFile=/etc/cloudflare-ddns.conf
StandardOutput=journal
StandardError=journal
Timer dosyası oluşturun:
# /etc/systemd/system/cloudflare-ddns.timer
[Unit]
Description=Cloudflare DDNS Guncelleme Zamanlayicisi
Requires=cloudflare-ddns.service
[Timer]
OnBootSec=30sec
OnUnitActiveSec=5min
AccuracySec=10sec
Persistent=true
[Install]
WantedBy=timers.target
Etkinleştirin ve başlatın:
systemctl daemon-reload
systemctl enable --now cloudflare-ddns.timer
# Durumu kontrol et
systemctl status cloudflare-ddns.timer
systemctl list-timers cloudflare-ddns.timer
# Manuel test
systemctl start cloudflare-ddns.service
journalctl -u cloudflare-ddns.service -f
Systemd timer’ın güzel yanı: sistem boot olduğunda ağ bağlantısını bekleyerek başlıyor ve her çalışma journald’a loglanıyor.
IPv6 (AAAA Kaydı) Desteği
Eğer ISS’niz IPv6 destekliyorsa AAAA kaydını da güncelleyebilirsiniz. Ancak IPv6’da biraz farklı çalışıyor çünkü birden fazla IPv6 adresiniz olabilir.
# Global scope IPv6 adresini bul (link-local ve ULA adreslerini atla)
get_ipv6() {
# ip komutundan global scope IPv6 al
ip -6 addr show scope global | grep -oP '(?<=inet6 )[da-f:]+(?=/[0-9]+ )' | head -1
}
IPV6=$(get_ipv6)
if [ -n "$IPV6" ]; then
curl -s -X PUT
"https://api.cloudflare.com/client/v4/zones/${CF_ZONE_ID}/dns_records/${CF_AAAA_RECORD_ID}"
-H "Authorization: Bearer ${CF_API_TOKEN}"
-H "Content-Type: application/json"
--data "{"type":"AAAA","name":"${CF_RECORD_NAME}","content":"${IPV6}","ttl":120,"proxied":false}"
fi
Hata Ayıklama ve İzleme
Script çalışmıyorsa veya DNS güncellenmiyor gibi görünüyorsa kontrol edin:
DNS yayılımını test edin:
# Cloudflare DNS sunucusuna direkt sor
dig @1.1.1.1 home.ornekdomain.com A +short
# Google DNS ile karsilastir
dig @8.8.8.8 home.ornekdomain.com A +short
# Mevcut IP ile karsilastir
curl -s https://api.ipify.org
Log takibi:
# Cron logu
tail -f /var/log/cloudflare-ddns.log
# Systemd journal
journalctl -u cloudflare-ddns.service --since "1 hour ago"
# Son 20 satir
journalctl -u cloudflare-ddns.service -n 20
Sık karşılaşılan sorunlar:
- “curl: not found”:
apt install curlveyadnf install curlile kurun - “Permission denied”: Script’in execute izni var mı?
chmod +x script.sh - API token hatası: Token’ın zone:dns:edit iznine sahip olduğunu kontrol edin
- Record ID yanlış:
dns_recordsendpoint’inden doğru ID’yi alın - IP servisi erişilemiyor: Birden fazla IP servisi tanımlayın, biri çalışmıyorsa diğerine geçsin
- Proxied=true sorunu: Eğer Cloudflare proxy açıksa DNS’ten Cloudflare IP’si görünür, kendi IP’niz değil. VPN veya direkt bağlantı için
proxied: falseolmalı
Gerçek Dünya Senaryoları
Senaryo 1 – Ev sunucusu ve self-hosted servisler: Evde Proxmox veya TrueNAS çalıştırıyorsunuz, Nextcloud, Jellyfin, Gitea gibi servisler var. ISS’niz dinamik IP veriyor. Bu script sayesinde nas.evim.com her zaman güncel IP’ye işaret ediyor.
Senaryo 2 – Uzak ofis VPN: Küçük bir ofisin fiber bağlantısı var ama statik IP yok. WireGuard veya OpenVPN sunucusunu ofis bilgisayarında çalıştırıyorsunuz. DDNS ile vpn.sirket.com adresi her zaman güncel kalıyor, çalışanlar IP ezberlemek zorunda kalmıyor.
Senaryo 3 – Test ve geliştirme ortamı: Geliştiriciler kendi makinelerinde webhook test etmek istiyor. Ngrok kullanmak yerine kendi DDNS sistemleri sayesinde dev.ornekdomain.com adresini test amaçlı kullanabiliyorlar.
Senaryo 4 – Raspberry Pi tabanlı servisler: Pi-hole, Home Assistant veya küçük bir dosya sunucusu çalıştırıyorsunuz. Elektrik kesilmesi veya router yeniden başlaması sonrası IP değişse bile sistem otomatik olarak güncelleniyor.
Güvenlik Notları
API Token izinlerini minimum tutun: Sadece ilgili zone için DNS:Edit izni yeterli. Tüm hesaba erişim veren Global API Key kullanmayın.
Config dosyasını koruyun: /etc/cloudflare-ddns.conf dosyasını chmod 600 ile sadece root okuyabilsin yapın. Eğer scripti sudo olmadan çalıştırıyorsanız, ilgili kullanıcı grubuna verin ama başkasına açmayın.
Log dosyalarında token yazdırmayın: Script hata ayıklamasında API token veya tam URL’yi loglamamaya dikkat edin.
Cloudflare Proxy değerlendirmesi: Eğer servisiniz web tabanlıysa proxied: true kullanmak IP’nizi gizler ve Cloudflare’in DDoS korumasından yararlanırsınız. Ancak non-HTTP servisler (VPN, SSH, oyun sunucuları) için proxied: false gerekir.
Sonuç
Cloudflare API ile DDNS kurmak düşündüğünden çok daha basit. Bir kez kurduğunuzda aylarca, yıllarca sorunsuz çalışır. Temel bash script ile başlayın, ihtiyaç arttıkça çoklu kayıt ve IPv6 desteğini ekleyin. Systemd timer kullanıyorsanız journalctl ile tüm geçmişi görebiliyorsunuz, cron’a göre çok daha iyi bir deneyim.
En önemli nokta: IP cache mekanizması. Her çalışmada API çağrısı yapmak yerine IP değiştiğinde güncelleme yapmak hem API rate limit’lerinizi koruyor hem de Cloudflare’in loglarını gereksiz yere şişirmiyor. Script’i indirip çalıştırmak yerine satır satır okuyup anlayın, kendi ortamınıza göre uyarlayın. Sysadmin işi biraz da bu.
