El DNS dinámico (DDNS) mantiene un nombre, por ejemplo casa.example.com, apuntando a un equipo cuya IP pública cambia, como un servidor en casa o en una oficina con conexión de IP dinámica. Un cliente en ese equipo detecta la IP actual y actualiza el registro DNS cuando cambia. En este tutorial configurarás ddclient en Ubuntu 24.04 para actualizar un registro en Cloudflare y, como alternativa, verás cómo hacerlo contra tu propio servidor BIND9 con nsupdate y una clave TSIG.

Requisitos previos

Para seguir esta guía necesitas:

  • El equipo con IP dinámica ejecutando Ubuntu 24.04 LTS, con un usuario no root con privilegios sudo.
  • Un dominio. Para el método principal, el dominio debe usar los servidores DNS de Cloudflare (el plan gratuito es suficiente).
  • Para el método alternativo, un servidor BIND9 autoritativo para tu dominio, por ejemplo en un VPS de CubePath, al que tengas acceso con sudo.

En los ejemplos se usa el dominio example.com y el nombre casa.example.com. Sustitúyelos por los tuyos.

Paso 1: Crear el registro y un token de API en Cloudflare

ddclient actualiza un registro existente, así que créalo primero. En el panel de Cloudflare, abre tu dominio, ve a DNS > Records y añade un registro:

  • Type: A
  • Name: casa
  • IPv4 address: cualquier IP provisional, por ejemplo 192.0.2.1
  • Proxy status: desactivado (DNS only), para que el nombre resuelva a tu IP real

Después crea un token con los permisos mínimos. Ve a My Profile > API Tokens > Create Token, elige Create Custom Token y configura:

  • Permissions: Zone / Zone / Read y Zone / DNS / Edit
  • Zone Resources: Include / Specific zone / example.com

Copia el token; Cloudflare solo lo muestra una vez. Comprueba que es válido sustituyendo tu_api_token:

curl -s https://api.cloudflare.com/client/v4/user/tokens/verify \
  -H "Authorization: Bearer tu_api_token"
{"result":{"id":"...","status":"active"},"success":true,"errors":[],"messages":[{"code":10000,"message":"This API Token is valid and active","type":null}]}

Paso 2: Instalar ddclient

ddclient está en los repositorios de Ubuntu. El paquete lanza un asistente de configuración que no necesitas, porque escribirás el archivo a mano; puedes evitarlo en modo no interactivo:

sudo apt update
sudo DEBIAN_FRONTEND=noninteractive apt install -y ddclient

Comprueba la versión:

dpkg -s ddclient | grep '^Version'
Version: 3.11.2-1

Paso 3: Configurar ddclient para Cloudflare

Abre el archivo de configuración:

sudo nano /etc/ddclient.conf

Sustituye todo su contenido por este bloque, con tu zona, tu token y tu nombre:

# Comprobar la IP cada 5 minutos y registrar en syslog
daemon=300
syslog=yes
ssl=yes

# Detectar la IP pública IPv4 mediante un servicio web
usev4=webv4, webv4=ipify-ipv4

# Cloudflare con token de API
protocol=cloudflare
zone=example.com
ttl=1
login=token
password=tu_api_token
casa.example.com

Algunos detalles de esta configuración:

  • usev4=webv4 hace que ddclient pregunte a un servicio externo cuál es tu IP pública. Es necesario cuando el equipo está detrás de un router con NAT, porque la IP de la interfaz sería privada.
  • login=token indica que password es un token de API y no la clave global de la cuenta.
  • ttl=1 significa TTL automático en Cloudflare.
  • La última línea es el nombre a actualizar. Puedes poner varios separados por comas.

El archivo contiene el token, así que restringe los permisos:

sudo chmod 600 /etc/ddclient.conf

Paso 4: Probar la actualización

Ejecuta ddclient una vez en primer plano con salida detallada:

sudo ddclient -daemon=0 -verbose -noquiet

Entre la salida debe aparecer la IP detectada y una línea de éxito similar a esta:

SUCCESS:  casa.example.com: IPv4 address set to 198.51.100.23

Comprueba el registro consultando directamente a los servidores autoritativos de Cloudflare, que no tienen caché intermedia. Averigua cuáles son:

dig +short NS example.com

Y consulta a uno de los nombres devueltos, que tienen la forma nombre.ns.cloudflare.com:

dig +short casa.example.com @your_ns.ns.cloudflare.com
198.51.100.23

Paso 5: Ejecutar ddclient como servicio

Con daemon=300, el servicio comprueba la IP cada 5 minutos y solo llama a la API cuando cambia. Actívalo y reinícialo para que lea la configuración nueva:

sudo systemctl enable ddclient
sudo systemctl restart ddclient
systemctl status ddclient --no-pager
● ddclient.service - Update dynamic domain name service entries
     Loaded: loaded (/usr/lib/systemd/system/ddclient.service; enabled; preset: enabled)
     Active: active (running)

Los mensajes de cada actualización quedan en el journal:

sudo journalctl -u ddclient -n 20 --no-pager

ddclient guarda la última IP enviada en /var/cache/ddclient/ddclient.cache. Si cambias el registro a mano en Cloudflare, ejecuta sudo ddclient -daemon=0 -force para forzar una actualización.

Alternativa: DDNS con tu propio BIND9 y nsupdate

Si el dominio lo sirve tu propio BIND9, no necesitas un proveedor externo: BIND admite actualizaciones dinámicas (RFC 2136) autenticadas con una clave TSIG compartida. Los pasos siguientes suponen que ya tienes la zona example.com funcionando en el servidor.

En el servidor BIND: crear la clave y permitir la actualización

Genera una clave TSIG y guárdala con permisos restringidos:

tsig-keygen -a hmac-sha256 ddns-casa | sudo tee /etc/bind/ddns-casa.key > /dev/null
sudo chown root:bind /etc/bind/ddns-casa.key
sudo chmod 640 /etc/bind/ddns-casa.key

BIND escribe en el archivo de zona y en su diario (.jnl) al aplicar actualizaciones, y en Ubuntu solo puede escribir en /var/lib/bind. Mueve allí el archivo de zona si estaba en /etc/bind:

sudo mv /etc/bind/db.example.com /var/lib/bind/db.example.com
sudo chown bind:bind /var/lib/bind/db.example.com

Edita la definición de la zona:

sudo nano /etc/bind/named.conf.local

Incluye la clave y usa update-policy para que solo pueda modificar el registro A de casa.example.com, nada más:

include "/etc/bind/ddns-casa.key";

zone "example.com" {
    type primary;
    file "/var/lib/bind/db.example.com";
    update-policy {
        grant ddns-casa name casa.example.com. A;
    };
};

Valida y recarga:

sudo named-checkconf
sudo rndc reload

Asegúrate de que el puerto 53/TCP y 53/UDP está abierto en el servidor. Copia el contenido de /etc/bind/ddns-casa.key al equipo de casa por un canal seguro (por ejemplo, scp).

En el equipo de casa: script de actualización

Instala nsupdate y dig (ambos en bind9-dnsutils) y curl:

sudo apt install -y bind9-dnsutils curl

Guarda la clave en un directorio accesible solo por root:

sudo install -d -m 700 /etc/ddns
sudo nano /etc/ddns/ddns-casa.key
sudo chmod 600 /etc/ddns/ddns-casa.key

Crea el script que compara la IP pública con la del DNS y solo actualiza si difieren:

sudo nano /usr/local/bin/ddns-nsupdate
#!/usr/bin/env bash
set -euo pipefail

SERVER="ns1.example.com"
ZONE="example.com"
RECORD="casa.example.com"
TTL=300
KEY="/etc/ddns/ddns-casa.key"

current_ip="$(curl -4 -fsS --max-time 10 https://api.ipify.org)"
if [[ ! "$current_ip" =~ ^[0-9]{1,3}(\.[0-9]{1,3}){3}$ ]]; then
    echo "Respuesta no válida al detectar la IP: ${current_ip}" >&2
    exit 1
fi

dns_ip="$(dig +short A "$RECORD" @"$SERVER" | head -n 1)"
if [[ "$current_ip" == "$dns_ip" ]]; then
    exit 0
fi

nsupdate -k "$KEY" <<EOF
server ${SERVER}
zone ${ZONE}
update delete ${RECORD}. A
update add ${RECORD}. ${TTL} A ${current_ip}
send
EOF

echo "${RECORD} actualizado: ${dns_ip:-sin registro} -> ${current_ip}"

Hazlo ejecutable y pruébalo:

sudo chmod 755 /usr/local/bin/ddns-nsupdate
sudo /usr/local/bin/ddns-nsupdate
casa.example.com actualizado: sin registro -> 198.51.100.23

Si lo ejecutas otra vez sin que cambie la IP, no muestra nada. En el servidor verás la actualización en sudo journalctl -u named.

Programar la actualización con un timer de systemd

Crea la unidad de servicio:

sudo nano /etc/systemd/system/ddns-nsupdate.service
[Unit]
Description=Actualizar casa.example.com con nsupdate
Wants=network-online.target
After=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/ddns-nsupdate

Crea el timer, que lo ejecuta 2 minutos después del arranque y luego cada 5 minutos:

sudo nano /etc/systemd/system/ddns-nsupdate.timer
[Unit]
Description=Ejecutar ddns-nsupdate cada 5 minutos

[Timer]
OnBootSec=2min
OnUnitActiveSec=5min

[Install]
WantedBy=timers.target

Actívalo y comprueba la próxima ejecución:

sudo systemctl daemon-reload
sudo systemctl enable --now ddns-nsupdate.timer
systemctl list-timers ddns-nsupdate.timer

Solución de problemas

ddclient no actualiza y el log dice que la IP no ha cambiado: compara la IP detectada con la de /var/cache/ddclient/ddclient.cache. Si el registro se cambió fuera de ddclient, fuerza la actualización con sudo ddclient -daemon=0 -force.

Error de autenticación en Cloudflare: vuelve a verificar el token con la llamada tokens/verify del paso 1 y confirma que tiene los permisos Zone Read y DNS Edit sobre la zona correcta.

update failed: REFUSED con nsupdate: el nombre o el tipo no coinciden con la regla grant de update-policy, o la clave del cliente no es la misma que la del servidor. El journal de named indica el motivo exacto.

NOTAUTH(BADTIME) o tsig verify failure: la hora de los dos equipos difiere demasiado. TSIG rechaza firmas con más de 5 minutos de desfase; comprueba que ambos sincronizan la hora con timedatectl.

permission denied al escribir el diario de la zona: el archivo de zona no está en /var/lib/bind o no pertenece al usuario bind.

Conclusión

Tu dominio sigue ahora la IP pública del equipo automáticamente, ya sea con ddclient contra la API de Cloudflare o con nsupdate contra tu propio BIND9, con credenciales limitadas a un único registro. Como siguientes pasos, puedes añadir un registro AAAA si tu conexión tiene IPv6, publicar servicios de casa detrás de un proxy inverso con TLS, o sustituir la exposición directa por una VPN como WireGuard.