Skip to content

Unbound Docker DNS récursif auto-hébergé et performant

Brandon
Date de publication:

💡 TL;DR

  • Unbound est un validateur DNS récursif et cache open-source développé par NLnet Labs.
  • En le conteneurisant avec Docker Compose, vous obtenez un resolver DNS local performant en quelques minutes.
  • C’est un complément idéal à un bloqueur comme AdGuard Home : Unbound résout, AdGuard filtre.

Pourquoi héberger son propre resolver DNS

Chaque fois que vous ouvrez une page web, votre machine envoie une requête DNS pour traduire un nom de domaine en adresse IP. Par défaut, cette requête part vers le serveur DNS de votre FAI, un Google 8.8.8.8 ou un Cloudflare 1.1.1.1. C’est rapide, mais vous dépendez d’un tiers et vous laissez trainer vos requêtes de résolution chez eux.

Unbound change la donne. C’est un resolver DNS récursif et cache qui tourne chez vous. Il va lui-même interroger les serveurs racine de l’Internet, puis les TLD, puis les autoritaires, pour remonter la réponse. Résultat : vous ne dépendez plus d’un DNS public, vous validez DNSSEC sur votre propre infrastructure et vous cachez agressivement les réponses pour accélérer considérablement les requêtes suivantes.

Bref, Unbound ne filtre pas comme AdGuard Home. Il résout. Et c’est déjà énorme.

Présentation d’Unbound

Unbound est un projet développé par NLnet Labs, la même maison qui maintient NSD et autres joyaux de l’infrastructure Internet. Distribué sous licence BSD, il est conçu pour la sécurité, la performance et la conformité aux standards. Contrairement à dnsmasq qui fait du forwarding vers un upstream, Unbound est un vrai résolveur récursif : il remonte la chaîne DNS depuis les root hints jusqu’au serveur autoritaire du domaine demandé.

Voici ce qu’il apporte nativement :

Pour l’homelab, c’est un outil de souveraineté réseau redoutable. Et en Docker, c’est un jeu d’enfant à déployer.

Unbound vs dnsmasq vs AdGuard Home : qui fait quoi

On confond souvent les outils DNS parce qu’ils tournent tous sur le port 53. Pourtant, leur rôle est fondamentalement différent :

OutilRôle principalRésolution récursiveFiltrage publicitaireCache
UnboundResolver DNS récursifOui, natifNonOui, très agressif
dnsmasqForwarder DNS / DHCPNon, relaye vers upstreamNonOui, léger
AdGuard HomeDNS filtrant avec UINon, relaye vers upstreamOui, listes intégréesOui

La confusion vient du fait qu’on peut chaîner ces outils. Par exemple, faire pointer AdGuard Home vers Unbound comme upstream. AdGuard fait le filtrage et le joli tableau de bord, Unbound fait la vraie résolution récursive. C’est d’ailleurs la stack idéale pour un homelab robuste : souveraineté + contrôle + sécurité.

Docker Compose : déployer Unbound en 5 minutes

L’image mvance/unbound est une des plus fiables et maintenues pour faire tourner Unbound en conteneur. Elle embarque une configuration de base fonctionnelle avec DNSSEC activé, QNAME minimisation et cache optimisé.

Vérifions les tags disponibles au moment où j’écris ces lignes : 1.22.0, 1.21.1, 1.21.0, 1.20.0 et latest. Pour un environnement de production, je recommande de pinner sur une version explicite plutôt que latest.

Créez votre docker-compose.yml :

services:
  unbound:
    image: mvance/unbound:1.22.0
    container_name: unbound
    restart: unless-stopped
    ports:
      - "53:53/tcp"
      - "53:53/udp"
    volumes:
      - ./unbound.conf:/opt/unbound/etc/unbound/unbound.conf:ro
      - unbound-data:/opt/unbound/etc/unbound/var
    environment:
      - PUID=1000
      - PGID=1000
    cap_add:
      - NET_BIND_SERVICE
    security_opt:
      - no-new-privileges:true
    networks:
      - dns-net

volumes:
  unbound-data:

networks:
  dns-net:
    driver: bridge

Le port 53 UDP est essentiel car DNS utilise majoritairement UDP. La capacité NET_BIND_SERVICE permet au conteneur non-root de binder sur un port privilégié. no-new-privileges:true est une bonne habitude de sécurité Docker.

Pourquoi choisir mvance/unbound plutôt qu’une image Alpine générique ? Cette image est maintenue par un contributeur actif de la communauté Unbound, elle embarque les root hints à jour, le fichier de trust anchor DNSSEC et une configuration de base déjà optimisée pour Docker. Vous pouvez aussi construire votre propre image à partir de alpine:latest avec le paquet unbound, mais vous devrez alors gérer vous-même les root hints et les permissions du conteneur. Pour débuter, mvance/unbound est le chemin de moindre résistance.

Ensuite, créez votre unbound.conf personnalisé :

server:
    verbosity: 1
    num-threads: 2
    interface: 0.0.0.0
    port: 53
    do-ip4: yes
    do-ip6: no
    do-udp: yes
    do-tcp: yes
    access-control: 127.0.0.0/8 allow
    access-control: 10.0.0.0/8 allow
    access-control: 172.16.0.0/12 allow
    access-control: 192.168.0.0/16 allow
    access-control: 0.0.0.0/0 refuse
    hide-identity: yes
    hide-version: yes
    qname-minimisation: yes
    harden-glue: yes
    harden-dnssec-stripped: yes
    harden-referral-path: yes
    unwanted-reply-threshold: 10000
    val-clean-additional: yes
    edns-buffer-size: 1232
    prefetch: yes
    prefetch-key: yes
    cache-min-ttl: 300
    cache-max-ttl: 86400
    msg-cache-slabs: 2
    rrset-cache-slabs: 2
    infra-cache-slabs: 2
    key-cache-slabs: 2
    rrset-cache-size: 128m
    msg-cache-size: 64m
    so-rcvbuf: 1m
    so-sndbuf: 1m
    private-address: 10.0.0.0/8
    private-address: 172.16.0.0/12
    private-address: 192.168.0.0/16

    # DNSSEC validation
    auto-trust-anchor-file: /opt/unbound/etc/unbound/var/root.key
    val-permissive-mode: no

    # Root hints
    root-hints: /opt/unbound/etc/unbound/var/root.hints

    # Logging
    log-queries: no
    logfile: ""
    use-syslog: no

    # Performance
    outgoing-range: 8192
    num-queries-per-thread: 4096

Démarrez le conteneur :

docker compose up -d

Vérifiez qu’il répond correctement depuis votre hôte ou une machine du réseau local :

dig @localhost google.com

Si vous voyez une réponse avec le flag ad (authenticated data) dans la section flags, c’est que DNSSEC est bien validé. Sinon, vérifiez que le fichier root.key est bien généré dans le volume.

Configurer vos clients pour utiliser Unbound

Une fois le conteneur actif, vous devez rediriger les requêtes DNS de vos appareils vers l’IP de votre serveur Docker.

Sur un routeur OpenWrt ou pfSense, renseignez l’IP de votre hôte Docker comme DNS unique. Sur vos postes Linux, modifiez /etc/resolv.conf ou utilisez systemd-resolved pour pointer vers votre instance Unbound. Si vous êtes en remote, pensez à sécuriser l’accès via un tunnel WireGuard ou un Cloudflare Tunnel plutôt que d’exposer le port 53 sur Internet.

Dans un setup avec AdGuard Home en frontal, configurez simplement l’upstream DNS d’AdGuard avec l’IP interne de votre conteneur Unbound (par exemple 10.0.0.5:53). AdGuard filtre, Unbound résout, vous dormez sur vos deux oreilles.

Pour un déploiement Docker Compose intégré, vous pouvez même les mettre sur le même réseau bridge personnalisé et utiliser le nom de service comme upstream :

# Extrait du compose AdGuard Home
services:
  adguardhome:
    image: adguard/adguardhome:v0.107.61
    networks:
      - dns-net
    # ...

  unbound:
    image: mvance/unbound:1.22.0
    networks:
      - dns-net
    # ...

Puis dans l’interface AdGuard Home, définissez l’upstream unbound:53. Le trafic DNS reste entièrement interne au réseau Docker, sans jamais sortir de votre serveur. C’est élégant, performant et totalement découplé des DNS publics.

Vérifier que tout fonctionne

Après avoir redirigé vos clients, testez avec plusieurs outils :

# Test de résolution classique
dig @192.168.1.100 google.com

# Vérifier DNSSEC (flag 'ad' doit apparaître)
dig @192.168.1.100 dnssec-failed.org

# Mesurer le temps de réponse
dig @192.168.1.100 +stats cloudflare.com | grep "Query time"

Le premier appel à un domaine inconnu prendra 20 à 60 millisecondes. Le second, grâce au cache, tombera sous la milliseconde. C’est là toute la magie d’un resolver local bien nourri.

Sécuriser avec DNSSEC et QNAME minimisation

Deux fonctionnalités font toute la différence entre un DNS de base et Unbound bien configuré.

DNSSEC garantit l’intégrité des réponses. Quand un domaine est signé, Unbound remonte la chaîne de confiance depuis la racine jusqu’au domaine et vérifie chaque signature. Si une réponse est falsifiée, Unbound la rejette. C’est une protection contre le DNS spoofing et certaines attaques Man-in-the-Middle. Notez que si DNSSEC échoue pour un domaine non signé, Unbound retourne quand même la réponse ; il n’est pas bloquant, juste vigilant.

QNAME minimisation réduit la fuite de données. Sans elle, votre resolver envoie le nom de domaine complet à chaque serveur intermédiaire (par exemple, demander à .com : “où est google.com ?”). Avec QNAME minimisation, il demande d’abord seulement “qui gère .com ?”, puis “qui gère google.com ?”, sans révéler le full query aux étapes précédentes. C’est défini dans la RFC 7816 et activé par défaut dans la configuration ci-dessus.

DNS-over-TLS : chiffrer les requêtes sortantes

Par défaut, Unbound fait de la résolution récursive en clair vers les serveurs racine et autoritaires. Si vous voulez chiffrer le trafic entre votre resolver et le reste du monde, vous pouvez configurer Unbound pour qu’il utilise DNS-over-TLS (DoT) vers un upstream de confiance pour les zones spécifiques, tout en gardant la résolution récursive pour le reste.

Dans la pratique, la plupart des utilisateurs conservent Unbound en mode récursif pur et ajoutent éventuellement une clause forward-zone pour certains TLD problématiques ou pour les requêtes internes. Voici un exemple de forward sécurisé vers Quad9 en DoT :

forward-zone:
    name: "."
    forward-ssl-upstream: yes
    forward-addr: 9.9.9.9@853
    forward-addr: 149.112.112.112@853

Attention : activer un forward global transforme Unbound en simple forwarder et annule l’intérêt de la résolution récursive directe. Utilisez cette config avec parcimonie, par exemple uniquement si votre FAI bloque les requêtes sortantes sur le port 53.

Cas d’usage concrets dans un homelab

Unbound n’est pas qu’un toy DNS pour geeks. Il résout des vrais problèmes :

Monitoring et maintenance

Unbound expose des statistiques via une commande interne. Vous pouvez les consulter avec :

docker exec unbound unbound-control stats_noreset

Cette commande renvoie des indicateurs précis : nombre de requêtes servies depuis le cache, taux de réponses NXDOMAIN, temps moyen de résolution, état des threads. Pour un homelab, les métriques essentielles à surveiller sont le cache hit ratio (idéalement au-dessus de 80 % après quelques jours) et le nombre de requêtes rejetées pour cause de validation DNSSEC échouée. Si ce dernier grimpe soudainement, vérifiez que votre fichier root.key est à jour et que l’horloge système est synchronisée, DNSSEC est exigeant sur la précision temporelle.

Si vous souhaitez des métriques Prometheus, des exporters existent comme github.com/letsencrypt/unbound_exporter. Pour un homelab classique, un simple check avec dig dans un cron et un webhook vers votre dashboard Beszel ou Netdata suffit amplement.

Voici un script de check minimal à placer dans votre crontab pour valider que le resolver répond toujours et que DNSSEC fonctionne :

#!/bin/bash
DNS_IP="192.168.1.100"

# Test résolution + DNSSEC flag
if dig @$DNS_IP +dnssec dnssec-failed.org | grep -q "SERVFAIL"; then
    echo "DNSSEC OK — domaine invalide bien rejeté"
else
    echo "ALERTE : DNSSEC non fonctionnel"
fi

# Test latence
LATENCY=$(dig @$DNS_IP +stats +nocmd google.com | awk '/Query time/{print $4}')
if [ "$LATENCY" -gt 100 ]; then
    echo "Latence élevée : ${LATENCY} ms"
else
    echo "Latence OK : ${LATENCY} ms"
fi

Le cache persiste dans le volume Docker, donc un redémarrage du conteneur ne le vide pas. Pensez à mettre à jour périodiquement les root hints et le trust anchor :

docker exec unbound unbound-anchor

Conclusion

Unbound est l’outil qu’il manquait à votre stack réseau. Léger, sécurisé, véritablement récursif et open-source, il vous rend souverain sur la résolution DNS de votre infrastructure. Conteneurisé avec Docker Compose, il s’intègre en cinq minutes dans un homelab existant et devient rapidement invisible tant il fait bien son travail.

Associez-le à un bloqueur comme AdGuard Home, sécurisez l’accès avec un tunnel VPN ou Cloudflare, et vous obtenez une chaîne DNS complète : filtrage, résolution récursive, validation DNSSEC, chiffrement possible. Pas besoin de faire confiance à Google pour savoir où se trouve votre propre NAS.

Le DNS, c’est la fondation de tout. Autant en reprendre le contrôle.

Next
Woodpecker CI Docker : CI/CD léger pour ton homelab