4.5 KiB
Porkbun DDNS — Dynamic DNS para carloselugo.com
Fecha: Abril 2026
Servidor: labmini-01 (Dell Optiplex)
Dominio: carloselugo.com
Ubicación del script: /opt/ddns/ddns_porkbun.sh
Objetivo
Detectar cambios en la IP pública del homelab y actualizar automáticamente los registros DNS en Porkbun — sin depender de un servicio DDNS de terceros.
Arquitectura
cron (cada 30 min)
→ ddns_porkbun.sh
→ obtiene IP pública (api.ipify.org)
→ compara con state file (~/.porkbun-ddns.last-ip)
→ si cambió → Porkbun API (editByNameType)
→ actualiza registros A
Registros DNS actualizados
| Subdominio | FQDN | Tipo |
|---|---|---|
@ |
carloselugo.com |
A |
gamesever1 |
gamesever1.carloselugo.com |
A |
git |
git.carloselugo.com |
A |
Nota:
*.carloselugo.comtiene un CNAME apuntando acarloselugo.com, por lo que cualquier subdominio nuevo hereda la IP del root automáticamente sin necesitar su propio registro A.
Archivos
| Archivo | Ruta |
|---|---|
| Script | /opt/ddns/ddns_porkbun.sh |
| State file | /home/netintel/.porkbun-ddns.last-ip |
| Log | /home/netintel/.porkbun-ddns.log |
Configuración del script
API_KEY="pk1_..." # Porkbun API key
SECRET_KEY="sk1_..." # Porkbun secret key
DOMAIN="carloselugo.com"
SUBDOMAINS=("@" "gamesever1" "git")
STATE_FILE="/home/netintel/.porkbun-ddns.last-ip"
LOG_FILE="/home/netintel/.porkbun-ddns.log"
Endpoint API usado
POST /dns/editByNameType/{domain}/A/{subdomain}
Se usa editByNameType en lugar de edit por ID porque es más robusto — no requiere hacer un retrieve previo para obtener el record ID, y evita el riesgo de crear registros duplicados accidentalmente.
Para el root (@) el endpoint es:
POST /dns/editByNameType/carloselugo.com/A
Para subdomains:
POST /dns/editByNameType/carloselugo.com/A/git
Prerequisitos en Porkbun
- Generar API key y secret en Account Settings → API Access
- Habilitar API access para el dominio en Domain Management → carloselugo.com → API Access (toggle)
Sin el toggle por dominio, el API devuelve
DOMAIN_IS_NOT_OPTED_IN_TO_API_ACCESSaunque las keys sean correctas.
Lógica del script
- Obtiene la IP pública via
api.ipify.org(fallback:ifconfig.me,icanhazip.com) - Lee la última IP conocida del state file
- Si son iguales → exit silencioso (no llama al API)
- Si son diferentes → por cada subdominio:
- Obtiene la IP actual en DNS via
retrieveByNameType - Si ya coincide → skip
- Si no → llama a
editByNameTypecon la nueva IP
- Obtiene la IP actual en DNS via
- Guarda la nueva IP en el state file
Cron
# crontab -e (usuario netintel)
*/30 * * * * /opt/ddns/ddns_porkbun.sh
Corre cada 30 minutos. Solo genera output en el log cuando la IP cambia.
Verificar funcionamiento
# Correr manualmente
/opt/ddns/ddns_porkbun.sh
# Ver log
tail -f ~/.porkbun-ddns.log
# Ver última IP registrada
cat ~/.porkbun-ddns.last-ip
# Forzar una IP específica (solo script de prueba)
/opt/ddns/ddns_porkbun_test.sh -f 1.2.3.4
Ejemplo de log
# Primera ejecución
[2026-04-28 11:44:54] IP cambió: (primera ejecución) → 24.139.235.46
[2026-04-28 11:44:55] [@] Ya actualizado (24.139.235.46)
[2026-04-28 11:44:56] [gamesever1] Ya actualizado (24.139.235.46)
[2026-04-28 11:44:57] [git] Ya actualizado (24.139.235.46)
# Cuando la IP cambia
[2026-04-28 12:00:00] IP cambió: 24.139.235.46 → 24.139.236.10
[2026-04-28 12:00:01] [@] ✓ 24.139.235.46 → 24.139.236.10
[2026-04-28 12:00:02] [gamesever1] ✓ 24.139.235.46 → 24.139.236.10
[2026-04-28 12:00:03] [git] ✓ 24.139.235.46 → 24.139.236.10
[2026-04-28 12:00:03] Listo. 3 registro(s) actualizado(s).
# Cuando no hay cambio (silencioso, no aparece en log)
Troubleshooting
El script no puede escribir el log
Verificar que las rutas de STATE_FILE y LOG_FILE sean escribibles por el usuario que corre el cron. Usar rutas en $HOME si no se corre como root.
Error: DOMAIN_IS_NOT_OPTED_IN_TO_API_ACCESS
El toggle de API access del dominio en Porkbun está desactivado. Activarlo en Domain Management.
El API conecta por IPv6 y falla
El servidor tiene conectividad IPv6. Todos los curl usan -4 para forzar IPv4 y evitar timeouts silenciosos.
El record desaparece después de editar
No usar dns/edit por ID cuando hay Cloudflare proxy habilitado en Porkbun — puede borrar el record. Usar siempre editByNameType.