💡 TL;DR
- Unbound est un résolveur DNS récursif, validant et cache, open source, développé par NLnet Labs.
- En le conteneurisant avec Docker Compose, tu obtiens un résolveur DNS local performant en quelques minutes.
- C’est un complément idéal à un bloqueur comme AdGuard Home : Unbound résout, AdGuard filtre.
Table des matières
Table des matières
- Pourquoi héberger ton propre résolveur DNS
- Présentation d’Unbound
- Unbound vs dnsmasq vs AdGuard Home : qui fait quoi
- Docker Compose : déployer Unbound en 5 minutes
- Configurer tes clients pour utiliser Unbound
- Sécuriser avec DNSSEC et QNAME minimisation
- DNS-over-TLS : chiffrer les requêtes sortantes
- Cas d’usage concrets dans un homelab
- Monitoring et maintenance
- Conclusion
Pourquoi héberger ton propre résolveur DNS
Chaque fois que tu ouvres une page web, ta 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 ton FAI, un Google 8.8.8.8 ou un Cloudflare 1.1.1.1. C’est rapide, mais tu dépends d’un tiers et tu laisses traîner tes requêtes de résolution chez lui.
Unbound change la donne. C’est un résolveur DNS récursif et cache qui tourne chez toi. Il interroge lui-même les serveurs racine d’Internet, puis ceux des TLD, puis les serveurs faisant autorité, pour remonter la réponse. Résultat : tu ne dépends plus d’un DNS public, tu valides DNSSEC sur ta propre infrastructure et tu gardes les réponses en cache pour accélérer nettement 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 développé par NLnet Labs, la même maison qui maintient NSD et d’autres briques de l’infrastructure d’Internet. Distribué sous licence BSD, il est conçu pour la sécurité, la performance et le respect des standards. Contrairement à dnsmasq, qui relaie les requêtes vers un serveur amont, Unbound est un vrai résolveur récursif : il remonte la chaîne DNS depuis les serveurs racine jusqu’au serveur qui fait autorité pour le domaine demandé.
Voici ce qu’il apporte nativement :
- Validation DNSSEC : il vérifie la signature cryptographique des réponses pour s’assurer qu’elles n’ont pas été altérées en route.
- QNAME minimisation (RFC 7816) : il n’envoie que le strict minimum d’informations aux serveurs intermédiaires, ce qui limite la fuite de tes données de navigation.
- Cache : une fois une réponse obtenue, elle est gardée en mémoire en respectant son TTL. Les requêtes suivantes sont quasi instantanées.
- DNS-over-TLS (DoT) : il peut chiffrer les requêtes sortantes vers un serveur amont de confiance.
- Root hints : il part de la liste officielle des serveurs racine, pas d’un DNS public.
Pour un 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 :
| Outil | Rôle principal | Résolution récursive | Filtrage publicitaire | Cache |
|---|---|---|---|---|
| Unbound | Résolveur DNS récursif | Oui, natif | Non | Oui, configurable |
| dnsmasq | Relais DNS / DHCP | Non, relaie vers un serveur amont | Non | Oui, léger |
| AdGuard Home | DNS filtrant avec interface | Non, relaie vers un serveur amont | Oui, listes intégrées | Oui |
La confusion vient du fait qu’on peut chaîner ces outils. Par exemple, faire pointer AdGuard Home vers Unbound comme serveur amont : 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 et sécurité.
Docker Compose : déployer Unbound en 5 minutes
L’image communautaire mvance/unbound est l’une des plus utilisées pour faire tourner Unbound en conteneur. Elle embarque une configuration de base fonctionnelle avec DNSSEC activé et QNAME minimisation.
Au moment où j’écris ces lignes, les tags disponibles sont 1.22.0, 1.21.1, 1.21.0, 1.20.0 et latest. En production, épingle une version explicite plutôt que latest.
Crée ton 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 en UDP est essentiel, car DNS passe majoritairement par UDP. La capacité NET_BIND_SERVICE permet au conteneur non-root d’écouter sur un port privilégié. no-new-privileges:true est une bonne habitude de sécurité Docker.
Pourquoi mvance/unbound plutôt qu’une image Alpine générique ? Elle embarque les root hints, le fichier de trust anchor DNSSEC et une configuration de base déjà adaptée à Docker. Tu peux aussi construire ta propre image à partir d’alpine avec le paquet unbound, mais tu devras alors gérer toi-même les root hints et les permissions du conteneur. Pour débuter, mvance/unbound est le chemin le plus simple.
Ensuite, crée ton 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
# Nécessaire pour unbound-control (statistiques), en local uniquement
remote-control:
control-enable: yes
control-interface: 127.0.0.1
control-use-cert: no
Démarre le conteneur :
docker compose up -d
Vérifie qu’il répond correctement depuis ton hôte ou une machine du réseau local :
dig @localhost cloudflare.com
Si tu vois le flag ad (authenticated data) dans la section flags, DNSSEC est bien validé : cloudflare.com est un domaine signé. Sinon, vérifie que le fichier root.key est bien présent dans le volume.
Configurer tes clients pour utiliser Unbound
Une fois le conteneur actif, redirige les requêtes DNS de tes appareils vers l’IP de ton serveur Docker.
Sur un routeur OpenWrt ou pfSense, renseigne l’IP de ton hôte Docker comme unique serveur DNS. Sur tes postes Linux, modifie /etc/resolv.conf ou passe par systemd-resolved pour pointer vers ton instance Unbound. Pour un accès à distance, passe par un tunnel WireGuard plutôt que d’exposer le port 53 sur Internet : un résolveur ouvert à tous sert vite de relais aux attaques par amplification.
Dans un setup avec AdGuard Home en frontal, configure simplement le serveur amont d’AdGuard avec l’IP interne de ton conteneur Unbound (par exemple 10.0.0.5:53). AdGuard filtre, Unbound résout, et tu dors sur tes deux oreilles.
Pour un déploiement Docker Compose intégré, tu peux même les mettre sur le même réseau bridge personnalisé et utiliser le nom de service comme serveur amont :
# 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 d’AdGuard Home, définis le serveur amont unbound:53. Le trafic DNS reste entièrement interne au réseau Docker, sans jamais sortir de ton serveur. C’est propre, performant et totalement découplé des DNS publics.
Vérifier que tout fonctionne
Après avoir redirigé tes clients, teste avec plusieurs commandes :
# Test de résolution classique (flag 'ad' attendu sur un domaine signé)
dig @192.168.1.100 cloudflare.com
# Vérifier que DNSSEC rejette un domaine volontairement mal signé : réponse SERVFAIL attendue
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 prend quelques dizaines de millisecondes. Le second, servi par le cache, tombe sous la milliseconde. C’est tout l’intérêt d’un résolveur local.
Sécuriser avec DNSSEC et QNAME minimisation
Deux fonctionnalités font toute la différence entre un DNS de base et un 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 la validation échoue, Unbound ne renvoie pas la réponse falsifiée mais une erreur SERVFAIL : c’est une protection contre l’empoisonnement de cache et certaines attaques de type man-in-the-middle. Les domaines qui ne sont pas signés du tout, eux, sont résolus normalement, sans le flag ad.
QNAME minimisation réduit la fuite de données. Sans elle, ton résolveur envoie le nom de domaine complet à chaque serveur intermédiaire (par exemple, il demande à .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 la requête complète aux étapes précédentes. C’est défini dans la RFC 7816 et activé 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 ceux qui font autorité. Si tu veux chiffrer le trafic entre ton résolveur et le reste du monde, tu peux configurer Unbound pour qu’il passe en DNS-over-TLS (DoT) vers un serveur amont de confiance pour certaines zones, tout en gardant la résolution récursive pour le reste.
Dans la pratique, la plupart des utilisateurs gardent Unbound en mode récursif pur et ajoutent éventuellement une clause forward-zone pour certains domaines ou pour les requêtes internes. Voici un exemple de relais chiffré 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 : un forward sur la zone "." transforme Unbound en simple relais et annule l’intérêt de la résolution récursive. Utilise cette configuration avec parcimonie, par exemple uniquement si ton FAI bloque les requêtes sortantes sur le port 53.
Cas d’usage concrets dans un homelab
Unbound n’est pas un jouet pour geeks. Il résout de vrais problèmes :
- DNS central du réseau local : au lieu de configurer chaque appareil avec 1.1.1.1, tu pointes tout vers Unbound. Tu contrôles la résolution, tu caches localement, tu réduis la latence perçue.
- Serveur amont d’AdGuard Home ou de Pi-hole : utilise Unbound comme unique serveur amont de ton filtreur pour ne plus dépendre de Google ni de Cloudflare.
- Réduction de latence : après quelques heures d’utilisation, le cache d’Unbound contient déjà la plupart des domaines que tu consultes régulièrement. Les requêtes passent de plusieurs dizaines de millisecondes à moins d’une.
- Résolution interne : avec des zones locales, tu peux résoudre tes propres noms internes (
nas.home.arpa,pve.home.arpa) sans passer par un DNS public.
Monitoring et maintenance
Unbound expose des statistiques via unbound-control, activé par la section remote-control de la configuration ci-dessus :
docker exec unbound unbound-control stats_noreset
Cette commande renvoie des indicateurs précis : nombre de requêtes servies depuis le cache, réponses NXDOMAIN, temps moyen de résolution, état des threads. Pour un homelab, les métriques essentielles sont le taux de réponses servies par le cache et le nombre de requêtes en échec de validation DNSSEC. Si ce dernier grimpe soudainement, vérifie que ton fichier root.key est à jour et que l’horloge du serveur est synchronisée : DNSSEC est exigeant sur l’heure.
Si tu veux des métriques Prometheus, il existe des exporters, comme github.com/letsencrypt/unbound_exporter. Pour un homelab classique, un simple check avec dig dans un cron, relié à ton outil de supervision (Beszel ou Netdata), suffit amplement.
Voici un script de vérification minimal à placer dans ta crontab, pour contrôler que le résolveur répond toujours et que DNSSEC fonctionne :
#!/bin/bash
DNS_IP="192.168.1.100"
# Test DNSSEC : un domaine volontairement mal signé doit être rejeté
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 d’Unbound vit en mémoire : un redémarrage du conteneur le vide, et il se reconstitue au fil des requêtes. Le volume ne conserve que les fichiers de travail, comme root.key. Pour garder le cache d’un redémarrage à l’autre, il faudrait le module cachedb avec un backend Redis, ce qui est rarement utile en homelab.
Le trust anchor DNSSEC (root.key) est tenu à jour automatiquement grâce à auto-trust-anchor-file. Pour le réinitialiser à la main, par exemple après une longue période hors ligne :
docker exec unbound unbound-anchor -a /opt/unbound/etc/unbound/var/root.key
💡 À lire aussi : Authelia Docker : authentification double facteur centralisée pour ton homelab, dans la même veine que cet article.
💡 À lire aussi : Headscale Docker : serveur Tailscale auto-hébergé (open-source), dans la même veine que cet article.
Conclusion
Unbound est l’outil qu’il manquait à ta stack réseau. Léger, sécurisé, véritablement récursif et open source, il te rend souverain sur la résolution DNS de ton infrastructure. Conteneurisé avec Docker Compose, il s’intègre en cinq minutes dans un homelab existant et se fait vite oublier tant il fait bien son travail.
Associe-le à un bloqueur comme AdGuard Home, garde l’accès distant derrière un VPN, et tu obtiens 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 ton propre NAS.
Le DNS, c’est la fondation de tout. Autant en reprendre le contrôle.