CoreDNS es un servidor DNS escrito en Go cuya funcionalidad se compone mediante plugins declarados en un único fichero, el Corefile. Es el DNS predeterminado de Kubernetes, pero también sirve como resolvedor con caché o servidor autoritativo para una red interna. En esta guía instalarás CoreDNS en Ubuntu 24.04 como servicio systemd y lo configurarás como resolvedor con caché para tu red privada, con reenvío por dominio y una zona interna propia.

Requisitos previos

  • Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath, con al menos 512 MB de RAM.
  • Un usuario no root con privilegios sudo.
  • La subred privada desde la que los clientes consultarán el DNS. En los ejemplos se usa 10.0.0.0/8; sustitúyela por la tuya.
  • dig para las pruebas, incluido en el paquete dnsutils:
sudo apt update
sudo apt install dnsutils

Paso 1: Descargar e instalar el binario

CoreDNS no está empaquetado en los repositorios de Ubuntu con una versión actual, así que se usa el binario oficial de GitHub. Consulta la última versión en la página de releases y ajusta la variable si es más nueva que la indicada. Si tu servidor es ARM, cambia amd64 por arm64.

COREDNS_VERSION="1.14.7"
cd /tmp
curl -LO "https://github.com/coredns/coredns/releases/download/v${COREDNS_VERSION}/coredns_${COREDNS_VERSION}_linux_amd64.tgz"
curl -LO "https://github.com/coredns/coredns/releases/download/v${COREDNS_VERSION}/coredns_${COREDNS_VERSION}_linux_amd64.tgz.sha256"

Verifica la suma de comprobación antes de instalar nada:

sha256sum -c "coredns_${COREDNS_VERSION}_linux_amd64.tgz.sha256"
coredns_1.14.7_linux_amd64.tgz: OK

Extrae el binario e instálalo en /usr/local/bin:

tar xzf "coredns_${COREDNS_VERSION}_linux_amd64.tgz"
sudo install -m 755 coredns /usr/local/bin/coredns
coredns -version
CoreDNS-1.14.7

La segunda línea de la salida indica la plataforma y la versión de Go con la que se compiló. Puedes listar los plugins compilados en el binario con coredns -plugins.

Paso 2: Crear el usuario y los directorios

CoreDNS no necesita ejecutarse como root. Crea un usuario de sistema sin shell y los directorios de configuración y zonas:

sudo useradd --system --no-create-home --shell /usr/sbin/nologin coredns
sudo mkdir -p /etc/coredns/zones

Los ficheros de /etc/coredns pertenecen a root y CoreDNS solo necesita leerlos, así que no hace falta cambiar su propietario.

Paso 3: Liberar el puerto 53

En Ubuntu 24.04, systemd-resolved ejecuta un stub DNS en 127.0.0.53:53 que impide a CoreDNS escuchar en todas las interfaces del puerto 53. Compruébalo:

sudo ss -lunp 'sport = :53'
State  Recv-Q Send-Q    Local Address:Port  Peer Address:Port Process
UNCONN 0      0         127.0.0.54:53            0.0.0.0:*     users:(("systemd-resolve",pid=612,fd=16))
UNCONN 0      0      127.0.0.53%lo:53            0.0.0.0:*     users:(("systemd-resolve",pid=612,fd=14))

En lugar de desinstalar systemd-resolved (del que dependen otros componentes del sistema), desactiva solo el stub con un fichero drop-in:

sudo mkdir -p /etc/systemd/resolved.conf.d
sudo nano /etc/systemd/resolved.conf.d/no-stub.conf
[Resolve]
DNSStubListener=no

Sin el stub, /etc/resolv.conf debe apuntar a la lista real de servidores que gestiona systemd-resolved, no a 127.0.0.53. Cambia el enlace simbólico y reinicia el servicio:

sudo ln -sf /run/systemd/resolve/resolv.conf /etc/resolv.conf
sudo systemctl restart systemd-resolved

Comprueba que el puerto 53 está libre (el comando no debe devolver ninguna línea con :53) y que el servidor sigue resolviendo nombres:

sudo ss -lunp 'sport = :53'
getent hosts ubuntu.com

Paso 4: Escribir el Corefile

El Corefile se organiza en bloques de servidor: cada bloque empieza por la zona que atiende (. significa "todo lo demás") y contiene los plugins que se aplican. El orden de los plugins dentro del bloque no importa, porque CoreDNS los ejecuta en un orden fijo definido al compilar.

Crea el fichero:

sudo nano /etc/coredns/Corefile
. {
    acl {
        allow net 10.0.0.0/8 127.0.0.0/8
        block
    }
    forward . 1.1.1.1 9.9.9.9 {
        health_check 5s
    }
    cache 300
    errors
    log . {
        class denial error
    }
    health 127.0.0.1:8080
    ready 127.0.0.1:8181
    prometheus 127.0.0.1:9153
    reload
    loop
}

Esto es lo que hace cada plugin:

  • acl: solo responde a la red privada y a localhost; el resto de clientes recibe REFUSED. Un resolvedor abierto a Internet se usa para ataques de amplificación DDoS, así que esta línea no es opcional.
  • forward: reenvía las consultas que no puede responder a Cloudflare (1.1.1.1) y Quad9 (9.9.9.9), comprobando cada 5 segundos si siguen respondiendo.
  • cache 300: guarda las respuestas hasta 300 segundos (o el TTL del registro, si es menor).
  • errors y log: registran errores y, para no llenar el journal, solo las consultas que terminan en NXDOMAIN o error.
  • health, ready y prometheus: endpoints de estado y métricas, limitados a localhost.
  • reload: vuelve a leer el Corefile automáticamente cuando cambia (lo comprueba cada 30 segundos).
  • loop: detecta bucles de reenvío y detiene CoreDNS en lugar de saturar la CPU.

Paso 5: Crear el servicio systemd

Crea la unidad de systemd:

sudo nano /etc/systemd/system/coredns.service
[Unit]
Description=CoreDNS DNS server
Documentation=https://coredns.io
After=network-online.target
Wants=network-online.target

[Service]
User=coredns
Group=coredns
ExecStart=/usr/local/bin/coredns -conf /etc/coredns/Corefile
ExecReload=/bin/kill -SIGUSR1 $MAINPID
Restart=on-failure
AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
LimitNOFILE=1048576

[Install]
WantedBy=multi-user.target

AmbientCapabilities=CAP_NET_BIND_SERVICE permite al usuario coredns abrir el puerto 53 sin ser root, sin tener que marcar el binario con setcap (que habría que repetir en cada actualización). Las opciones Protect* impiden que el proceso escriba en el sistema de ficheros. SIGUSR1 hace que CoreDNS recargue el Corefile.

Arranca y habilita el servicio:

sudo systemctl daemon-reload
sudo systemctl enable --now coredns
systemctl status coredns --no-pager
● coredns.service - CoreDNS DNS server
     Loaded: loaded (/etc/systemd/system/coredns.service; enabled; preset: enabled)
     Active: active (running) since Thu 2026-09-24 11:02:41 UTC; 3s ago

Si el servicio no arranca, journalctl -u coredns -n 50 muestra el motivo (normalmente un error de sintaxis en el Corefile o el puerto 53 ocupado).

Paso 6: Probar la resolución y la caché

Consulta un dominio público a través de CoreDNS:

dig @127.0.0.1 ubuntu.com +noall +answer +stats | grep -E 'IN|Query time'
ubuntu.com.		300	IN	A	185.125.190.20
;; Query time: 18 msec

Repite la consulta. La segunda respuesta sale de la caché: el tiempo baja a 0 ms y el TTL va disminuyendo:

ubuntu.com.		291	IN	A	185.125.190.20
;; Query time: 0 msec

Comprueba también los endpoints de estado:

curl -s http://127.0.0.1:8080/health; echo
curl -s http://127.0.0.1:8181/ready; echo
OK
OK

Paso 7: Añadir una zona interna autoritativa

Con el plugin file, CoreDNS responde con autoridad para una zona a partir de un fichero en formato estándar (RFC 1035), el mismo que usa BIND. En este ejemplo se usa corp.internal: el dominio de nivel superior .internal está reservado para uso privado, por lo que nunca chocará con un dominio real.

Crea el fichero de zona:

sudo nano /etc/coredns/zones/db.corp.internal
$ORIGIN corp.internal.
$TTL 300

@       IN  SOA ns1.corp.internal. hostmaster.corp.internal. (
            2026092401 ; serial (AAAAMMDDNN)
            3600       ; refresh
            900        ; retry
            604800     ; expire
            300 )      ; TTL negativo

@       IN  NS      ns1.corp.internal.
ns1     IN  A       10.0.0.53

web     IN  A       10.0.1.10
api     IN  A       10.0.1.20
db      IN  A       10.0.1.30
www     IN  CNAME   web.corp.internal.

Sustituye 10.0.0.53 por la IP privada de tu servidor CoreDNS y los demás registros por los tuyos. Después, añade un bloque para la zona al final del Corefile:

sudo nano /etc/coredns/Corefile
corp.internal {
    acl {
        allow net 10.0.0.0/8 127.0.0.0/8
        block
    }
    file /etc/coredns/zones/db.corp.internal {
        reload 30s
    }
    errors
}

CoreDNS elige siempre el bloque con la zona más específica, así que las consultas a corp.internal las atiende este bloque y el resto sigue yendo al bloque .. La opción reload 30s vuelve a leer el fichero de zona cuando cambia su número de serie, por lo que para publicar cambios basta con editar los registros e incrementar el serial.

El plugin reload del bloque . aplicará el nuevo Corefile en unos 30 segundos. Para aplicarlo ya:

sudo systemctl reload coredns

Comprueba los registros:

dig @127.0.0.1 web.corp.internal +short
dig @127.0.0.1 www.corp.internal +short
10.0.1.10
web.corp.internal.
10.0.1.10

La respuesta debe llevar el indicador aa (authoritative answer). Compruébalo con dig @127.0.0.1 api.corp.internal | grep flags.

Paso 8: Reenviar un dominio a otro servidor DNS

Es habitual que un dominio concreto lo resuelva otro servidor: un Active Directory, una VPN o el DNS de un clúster. Para eso basta un bloque con su propio forward. Por ejemplo, para enviar ad.example.com a los controladores de dominio 10.0.5.10 y 10.0.5.11, añade al Corefile:

ad.example.com {
    acl {
        allow net 10.0.0.0/8 127.0.0.0/8
        block
    }
    forward . 10.0.5.10 10.0.5.11
    cache 60
    errors
}

Recarga con sudo systemctl reload coredns y prueba con dig @127.0.0.1 dc01.ad.example.com.

Añadir entradas estáticas con hosts

Para unos pocos nombres sueltos no hace falta una zona: el plugin hosts acepta entradas en línea con el formato de /etc/hosts. Añádelo dentro del bloque .:

    hosts {
        10.0.2.50 legacy-app.lan
        10.0.2.51 old-db.lan
        fallthrough
    }

fallthrough es importante: sin él, cualquier nombre que no esté en la lista devolvería NXDOMAIN en lugar de pasar a forward.

Paso 9: Abrir el firewall y usar CoreDNS desde los clientes

Permite el DNS solo desde la red privada. Sustituye la subred por la tuya:

sudo ufw allow from 10.0.0.0/8 to any port 53 proto udp
sudo ufw allow from 10.0.0.0/8 to any port 53 proto tcp

No abras los puertos 8080, 8181 y 9153: en esta configuración solo escuchan en localhost.

Desde otro servidor de la red privada, sustituye coredns_private_ip por la IP privada de CoreDNS y comprueba que responde:

dig @coredns_private_ip web.corp.internal +short
10.0.1.10

Para que un cliente Ubuntu use CoreDNS de forma permanente, configura el servidor DNS de su interfaz en Netplan (clave nameservers.addresses) y aplica los cambios con sudo netplan apply.

CoreDNS en Kubernetes

En Kubernetes, CoreDNS se ejecuta como Deployment en el namespace kube-system y su Corefile vive en un ConfigMap. Puedes ver la configuración actual con:

kubectl -n kube-system get configmap coredns -o yaml

Para reenviar un dominio interno (por ejemplo corp.internal hacia el servidor que acabas de montar), edita el ConfigMap y añade un bloque nuevo junto al .:53 existente, sin tocar este:

kubectl -n kube-system edit configmap coredns
corp.internal:53 {
    errors
    cache 30
    forward . 10.0.0.53
}

El Corefile predeterminado de Kubernetes incluye el plugin reload, así que los pods de CoreDNS aplican el cambio en uno o dos minutos sin reiniciarlos. En clústeres gestionados (por ejemplo, con un addon que reescribe el ConfigMap) consulta la documentación de la distribución, porque tus cambios podrían sobrescribirse.

Solución de problemas

Listen: listen tcp :53: bind: address already in use

Otro proceso ocupa el puerto 53. Identifícalo con sudo ss -lntup 'sport = :53'. Si es systemd-resolve, revisa el Paso 3; si es dnsmasq o named, detenlo y deshabilítalo.

Loop detected en el journal

El plugin loop ha detectado que CoreDNS se reenvía consultas a sí mismo. Suele pasar si usas forward . /etc/resolv.conf y ese fichero apunta a 127.0.0.1. Usa servidores upstream explícitos, como en el Paso 4.

Los clientes reciben REFUSED

La IP del cliente no está permitida en el plugin acl. Revisa la subred en cada bloque y recarga el servicio.

Un cambio en el fichero de zona no se aplica

El plugin file solo recarga la zona si el número de serie del registro SOA aumenta. Increméntalo en cada cambio y consulta journalctl -u coredns -n 20 para ver si hay errores de sintaxis en la zona.

Conclusión

Tienes CoreDNS funcionando en Ubuntu 24.04 como servicio systemd sin privilegios de root, actuando como resolvedor con caché restringido a tu red privada, con una zona interna autoritativa y reenvío por dominio. Como siguientes pasos, puedes añadir un segundo servidor CoreDNS con la misma configuración para tener redundancia, recoger las métricas del puerto 9153 con Prometheus o cifrar el reenvío hacia los upstream con DNS over TLS (forward . tls://1.1.1.1 con tls_servername cloudflare-dns.com).