Aller au contenu
Virtualisation medium

Dépannage réseau KVM/libvirt

60 min de lecture

Logo KVM

Ce guide de dépannage réseau KVM/libvirt couvre les problèmes les plus fréquents : VM sans IP, NAT cassé, bridge non fonctionnel, DNS KO, conflits Docker, firewall guest bloquant. Chaque section propose un diagnostic précis et une solution testée.

Ce que vous trouverez :

  • Diagnostic Host vs Guest, identifier où ça coince (50% des pannes sont côté VM)
  • Recettes par topologie, NAT, Bridge, DNS en onglets séparés
  • Services modularisés, virtnetworkd vs libvirtd sur distros récentes
  • Firewall backend, nftables/iptables selon votre distribution
  • Pièges classiques, virsh domifaddr vide, virbr0 in use, conflits Docker

Utilisez l'arbre de décision pour aller directement à votre problème, ou parcourez les onglets NAT/Bridge/DNS.

Avant de plonger dans le dépannage, identifiez ça coince. 50% des pannes sont côté VM (guest), pas côté hôte.

VérificationCôtéCommande
Réseau libvirt actifHôtevirsh net-list --all
Bridge existeHôteip link show virbr0 ou br0
Services libvirtHôtesystemctl status virtnetworkd
Interface upVMip -br link
IP attribuéeVMip -br addr
Route par défautVMip route
DNS fonctionnelVMresolvectl status ou cat /etc/resolv.conf

Le schéma résume les embranchements, les huit sous-sections qui suivent les détaillent. Partez du symptôme le plus proche du vôtre et suivez les liens : chaque entrée renvoie directement à la recette correspondante plus bas dans la page. L'étape 0 n'est pas optionnelle, confirmer la topologie évite de passer une heure à dépanner un NAT alors que la VM est en bridge.

Arbre de décision réseau KVM/libvirt

0) Confirmer la topologie (sinon vous dépannez le mauvais réseau)

Section intitulée « 0) Confirmer la topologie (sinon vous dépannez le mauvais réseau) »

Avant tout diagnostic, vérifiez sur quelle topologie vous êtes réellement :

Fenêtre de terminal
# Quelle interface la VM utilise ?
virsh domiflist ma-vm
# Quels réseaux libvirt existent ?
virsh net-list --all
Source affichéeTopologieDHCP fourni par
default / virbr0NATdnsmasq (libvirt)
br0BridgeDHCP du LAN
Autre nomRéseau customDépend de la config

Une adresse en 169.254.x.x n'est pas une IP valide : c'est une adresse link-local que le système s'attribue lui-même quand aucune réponse DHCP n'arrive. Le symptôme est donc identique à l'absence totale d'IP, et se dépanne des deux côtés.

Le DHCP a répondu, la couche 2 fonctionne donc. Si le ping de la passerelle échoue malgré tout, le blocage est local à la VM ou vient d'un mauvais rattachement de l'interface virtuelle.

À ce stade, la VM parle à l'hôte mais l'hôte ne relaie pas ses paquets. Le coupable est presque toujours l'une de ces trois causes, toutes situées côté hôte : le routage désactivé, les règles de traduction d'adresses absentes, ou un autre outil qui les a écrasées.

Le routage fonctionne, seule la résolution de noms échoue. En NAT, le serveur DNS de la VM doit être 192.168.122.1, c'est-à-dire le dnsmasq lancé par libvirt ; les deux causes ci-dessous correspondent aux deux façons dont ce chemin peut se rompre.

5) Bridge ne marche pas / VM pas joignable depuis LAN

Section intitulée « 5) Bridge ne marche pas / VM pas joignable depuis LAN »

En mode bridge, la VM apparaît comme une machine physique de plus sur le LAN, avec sa propre adresse MAC. Tout ce qui filtre les adresses MAC en amont casse ce mode, et la cause numéro un est le Wi-Fi, à vérifier avant tout le reste.

Une panne qui apparaît puis disparaît vient rarement de libvirt : elle signale un tiers qui modifie l'état du système après coup. Cherchez ce qui a démarré entre le moment où ça marchait et le moment où ça a cessé.

Ce cas n'est pas une panne réseau : la VM a bien une adresse, c'est l'hôte qui ne sait pas la lire. La commande interroge par défaut les baux DHCP de libvirt, une source vide dès que la VM utilise une IP statique ou un bridge externe.


Ce test remonte les couches réseau une par une, et la première étape qui échoue désigne le problème. Exécutez les quatre commandes dans l'ordre, depuis la VM : inutile de continuer après le premier échec, les suivantes échoueront forcément aussi. Le tableau qui suit traduit chaque combinaison de résultats en section de dépannage.

Fenêtre de terminal
# 1. Interface up et IP ?
ip -br link
ip -br addr
# 2. Ping gateway
ping -c2 192.168.122.1 # NAT
# ou
ping -c2 <gateway-lan> # Bridge
# 3. Ping Internet
ping -c2 8.8.8.8
# 4. DNS
nslookup example.com
RésultatDiagnosticSection
(1) KO : pas d'IPDHCP host ou guestPas d'IP
(1) OK, (2) KOL2/L3 local, firewall guestFirewall guest
(2) OK, (3) KONAT/forwardingip_forward + NAT
(3) OK, (4) KODNSDNS

Les recettes qui suivent sont regroupées par topologie parce que les mêmes symptômes n'ont pas les mêmes causes selon le mode réseau. En NAT, c'est libvirt qui fournit DHCP et DNS, donc c'est chez lui qu'on cherche ; en bridge, ces services viennent du LAN et libvirt n'y est pour rien. Ouvrez l'onglet correspondant à ce que virsh domiflist a renvoyé plus haut.

Symptôme : virsh net-list --all montre "default" en état "inactive".

Fenêtre de terminal
# Démarrer et activer au boot
virsh net-start default
virsh net-autostart default

Si ça échoue avec "Network is already in use by interface virbr0", voir virbr0 already in use.

libvirt lance un processus dnsmasq pour fournir DHCP et DNS aux VMs. S'il est absent, pas de DHCP.

Fenêtre de terminal
# Vérifier le processus
ps aux | grep dnsmasq | grep virbr0

Si absent :

Fenêtre de terminal
# Redémarrer le réseau (relance dnsmasq)
virsh net-destroy default
virsh net-start default

Avant de supposer que le DHCP ne marche pas, vérifiez les baux existants :

Fenêtre de terminal
# Voir les IPs attribuées par libvirt
virsh net-dhcp-leases default
# Ou directement le fichier
cat /var/lib/libvirt/dnsmasq/default.leases

Si la VM a un bail mais virsh domifaddr ne montre rien, c'est un problème de source (voir piège ci-dessous).

virsh domifaddr peut renvoyer "rien" alors que la VM a une IP. Par défaut, il utilise la source "lease" qui peut être vide si le bail a expiré ou si la VM a une IP statique.

Testez les différentes sources :

Fenêtre de terminal
virsh domifaddr ma-vm --source lease # depuis les baux DHCP
virsh domifaddr ma-vm --source arp # depuis la table ARP
virsh domifaddr ma-vm --source agent # via qemu-guest-agent

Pour fiabiliser --source agent, installez qemu-guest-agent dans la VM :

Fenêtre de terminal
# Debian/Ubuntu
sudo apt install qemu-guest-agent
sudo systemctl enable --now qemu-guest-agent
# RHEL/Rocky/Alma
sudo dnf install qemu-guest-agent
sudo systemctl enable --now qemu-guest-agent

Symptôme : La VM a une IP (192.168.122.x), peut ping la gateway (192.168.122.1), mais pas 8.8.8.8.

  1. Vérifier ip_forward

    Fenêtre de terminal
    cat /proc/sys/net/ipv4/ip_forward
    # Doit être 1

    Si 0 :

    Fenêtre de terminal
    # Activer de façon permanente
    echo "net.ipv4.ip_forward = 1" | sudo tee /etc/sysctl.d/99-ip-forward.conf
    sudo sysctl -p /etc/sysctl.d/99-ip-forward.conf
  2. Vérifier les règles NAT (voir section Firewall backend)

  3. Redémarrer le réseau libvirt pour régénérer les règles :

    Fenêtre de terminal
    virsh net-destroy default
    virsh net-start default

Symptôme : Côté hôte tout est OK (dnsmasq tourne, leases disponibles), mais la VM n'a pas d'IP.

C'est très fréquent avec les cloud images minimalistes ou après modification de la config réseau.

Diagnostic dans la VM :

Fenêtre de terminal
# Interface up ?
ip -br link
# Si DOWN : sudo ip link set eth0 up
# Client DHCP actif ?
# Debian/Ubuntu (systemd-networkd)
systemctl status systemd-networkd
# Debian/Ubuntu (NetworkManager)
nmcli dev status
# RHEL/Rocky/Alma
nmcli connection show

Solutions selon le gestionnaire réseau :

Fenêtre de terminal
# Vérifier la config
cat /etc/systemd/network/*.network
# Exemple de config DHCP
cat << 'EOF' | sudo tee /etc/systemd/network/20-dhcp.network
[Match]
Name=en* eth*
[Network]
DHCP=yes
EOF
# Redémarrer
sudo systemctl restart systemd-networkd

Symptôme : Après clonage d'une VM, le réseau ne fonctionne plus ou est aléatoire.

Causes :

  1. machine-id dupliqué : Le DHCP attribue la même IP à toutes les clones
  2. Cloud-Init ne rejoue pas : La config réseau n'est pas régénérée
  3. Netplan avec config statique : L'ancienne IP/MAC est hardcodée

Solutions :

Fenêtre de terminal
# 1. Régénérer le machine-id
sudo rm /etc/machine-id
sudo systemd-machine-id-setup
# 2. Forcer Cloud-Init à rejouer
sudo cloud-init clean
sudo cloud-init init
# 3. Si netplan : vérifier qu'il n'y a pas de MAC/IP hardcodée
cat /etc/netplan/*.yaml

Sur les distributions récentes, libvirt est modularisé. La partie réseau est gérée par virtnetworkd, pas libvirtd.

Fenêtre de terminal
# Vérifier quel daemon est actif
systemctl status libvirtd # daemon monolithique (ancien)
systemctl status virtnetworkd # daemon réseau (nouveau)
systemctl status virtqemud # daemon QEMU (nouveau)

Interrogez le journal du daemon qui gère réellement le réseau chez vous, sinon vous lirez un journal muet. La fenêtre --since évite de remonter des jours d'historique, ajustez-la à l'heure de l'incident.

Fenêtre de terminal
# Daemon modularisé (préféré)
journalctl -u virtnetworkd --since "10 minutes ago"
# Daemon monolithique
journalctl -u libvirtd --since "10 minutes ago"
# Logs dnsmasq (si syslog)
sudo grep dnsmasq /var/log/syslog | tail -20

libvirt gère automatiquement les règles firewall pour le NAT, mais selon le backend (iptables ou nftables), le comportement diffère.

Commencez par savoir quel outil interroger : sur une machine où libvirt utilise nftables, les commandes iptables renverront une sortie vide ou trompeuse. La commande qui affiche des règles est celle du bon backend.

Fenêtre de terminal
# Fedora/RHEL 9+ : nftables par défaut
sudo nft list ruleset | grep -i libvirt | head -5
# Debian/Ubuntu : souvent iptables
sudo iptables -t nat -L -n | grep -i virbr

Trois règles suffisent à faire fonctionner le NAT, et l'absence d'une seule produit un symptôme distinct : sans MASQUERADE les paquets sortent avec une IP privée et ne reviennent jamais, sans la règle FORWARD ils sont jetés sur l'hôte, sans la règle INPUT la VM n'obtient même pas d'adresse.

RègleRôle
MASQUERADE (POSTROUTING)Traduit les IPs des VMs vers l'IP de l'hôte
ACCEPT (FORWARD)Autorise le trafic des VMs vers l'extérieur
ACCEPT (INPUT)Autorise DHCP (port 67) et DNS (port 53) vers dnsmasq
Fenêtre de terminal
# nftables
sudo nft list chain ip nat POSTROUTING 2>/dev/null | grep -i masq
# iptables
sudo iptables -t nat -S POSTROUTING | grep -i virbr
sudo iptables -S FORWARD | grep -i virbr

libvirt réinstalle ses règles au démarrage du réseau, il n'y a donc rien à écrire à la main. Attention, net-destroy coupe la connectivité de toutes les VM attachées à ce réseau le temps du redémarrage.

Fenêtre de terminal
# Redémarrer le réseau régénère les règles
virsh net-destroy default
virsh net-start default
# Si ça ne suffit pas, redémarrer le daemon réseau
sudo systemctl restart virtnetworkd

Docker et libvirt peuvent entrer en conflit : Docker modifie ip_forward, ajoute ses propres règles NAT, et peut interférer avec les bridges.

L'objectif est de constater la cohabitation : lister les bridges des deux outils, puis comparer leurs règles de traduction d'adresses. L'ordre des règles compte autant que leur présence, un DOCKER placé avant les chaînes libvirt intercepte le trafic des VM.

Fenêtre de terminal
# Voir tous les bridges
ip -br link | grep -E 'docker0|virbr0|br0'
# Vérifier les règles NAT de Docker vs libvirt
sudo nft list ruleset | grep -nE 'docker|libvirt' | head -20
# ou
sudo iptables -t nat -L -n | grep -E 'DOCKER|virbr'

Les trois correctifs traitent chacun une interaction différente et se cumulent. Le troisième donne la règle générale à retenir : quand les deux outils cohabitent, libvirt doit reposer ses règles en dernier.

  1. Docker désactive ip_forward au redémarrage : ajoutez explicitement dans sysctl :

    Fenêtre de terminal
    echo "net.ipv4.ip_forward = 1" | sudo tee /etc/sysctl.d/99-ip-forward.conf
  2. Collision de subnet : Docker utilise 172.17.0.0/16, libvirt 192.168.122.0/24. Normalement pas de conflit, mais vérifiez :

    Fenêtre de terminal
    ip route | grep -E '172.17|192.168.122'
  3. Règles iptables écrasées : redémarrez le réseau libvirt après Docker :

    Fenêtre de terminal
    sudo systemctl restart docker
    virsh net-destroy default && virsh net-start default

Symptôme : IP OK, ping gateway OK, mais rien d'autre ne passe.

C'est souvent le firewall de la VM qui bloque. Vérifiez dans la VM :

Fenêtre de terminal
# iptables
sudo iptables -S | head -20
# nftables
sudo nft list ruleset 2>/dev/null | head -20
# UFW (Ubuntu)
sudo ufw status
# firewalld (RHEL/Fedora)
sudo firewall-cmd --list-all

Ces commandes servent uniquement à confirmer un diagnostic, sur une VM de test et pour quelques minutes. Un iptables -F vide toutes les chaînes sans possibilité d'annulation, et ufw disable survit au redémarrage.

Fenêtre de terminal
# UFW
sudo ufw disable
# firewalld
sudo systemctl stop firewalld
# iptables (flush)
sudo iptables -F

Ces deux messages arrivent au démarrage du réseau libvirt, avant même qu'une VM ne soit lancée. Ils partagent une même origine, un désaccord entre l'état du système et la définition XML du réseau : un bridge qui traîne alors que libvirt le croit absent, ou une définition disparue alors que le bridge existe encore.

Symptôme :

error: Failed to start network default
error: internal error: Network is already in use by interface virbr0

Causes :

  1. Le bridge virbr0 existe mais n'est pas géré par libvirt
  2. Collision avec un autre réseau libvirt

Diagnostic :

Fenêtre de terminal
# Voir si virbr0 existe
ip link show virbr0
# Voir la définition du réseau
virsh net-dumpxml default | grep -E 'bridge|ip address'
# Vérifier les routes
ip route | grep 192.168.122

Solution :

Fenêtre de terminal
# Si virbr0 est orphelin, le supprimer
sudo ip link delete virbr0 2>/dev/null
# Puis redémarrer
virsh net-start default

Symptôme : virsh net-list --all ne montre pas "default".

Fenêtre de terminal
# Vérifier si le fichier XML existe
ls /etc/libvirt/qemu/networks/default.xml
ls /usr/share/libvirt/networks/default.xml
# Recréer depuis le template
virsh net-define /usr/share/libvirt/networks/default.xml
virsh net-start default
virsh net-autostart default

Si le fichier template n'existe pas, créez-le :

Fenêtre de terminal
cat << 'EOF' | sudo tee /tmp/default-network.xml
<network>
<name>default</name>
<forward mode='nat'/>
<bridge name='virbr0' stp='on' delay='0'/>
<ip address='192.168.122.1' netmask='255.255.255.0'>
<dhcp>
<range start='192.168.122.2' end='192.168.122.254'/>
</dhcp>
</ip>
</network>
EOF
virsh net-define /tmp/default-network.xml
virsh net-start default
virsh net-autostart default

Quand le diagnostic n'aboutit pas, repartir d'une configuration propre coûte souvent moins cher que de continuer à chercher. Les deux procédures ci-dessous détruisent la configuration réseau existante avant de la recréer depuis le modèle fourni par libvirt : toute personnalisation (plage DHCP modifiée, réservations d'adresses, port forwarding) est perdue et devra être réappliquée.

Les quatre étapes s'exécutent dans cet ordre strict : arrêter les VM concernées, supprimer la définition, retirer le bridge orphelin, puis redéfinir depuis le modèle. Sauter l'arrêt des VM laisse le bridge occupé et fait échouer la suite.

Fenêtre de terminal
# 1. Arrêter toutes les VMs utilisant ce réseau
for vm in $(virsh list --name); do
if virsh domiflist $vm | grep -q default; then
virsh shutdown $vm
fi
done
# 2. Supprimer le réseau
virsh net-destroy default 2>/dev/null
virsh net-undefine default 2>/dev/null
# 3. Supprimer le bridge orphelin
sudo ip link delete virbr0 2>/dev/null
# 4. Recréer
virsh net-define /usr/share/libvirt/networks/default.xml
virsh net-start default
virsh net-autostart default

Cette procédure ramène l'hôte à une connexion réseau directe, sans bridge. Ne la lancez jamais depuis une session SSH passant par l'interface concernée : la suppression du bridge coupe la connexion avant que la ligne suivante ne s'exécute, et l'hôte reste injoignable.

Fenêtre de terminal
# 1. Supprimer le bridge
sudo nmcli connection delete br0 2>/dev/null
sudo nmcli connection delete bridge-slave-eno1 2>/dev/null
# 2. Réactiver la connexion directe
sudo nmcli connection up "Wired connection 1"

Ce récapitulatif rassemble les commandes utilisées dans la page, regroupées par objectif, pour servir d'aide-mémoire lors d'un incident. Toutes sont en lecture seule, vous pouvez les enchaîner sans risque avant de toucher à quoi que ce soit. Sauf mention contraire, elles se lancent sur l'hôte.

Ces quatre commandes établissent la photo de départ : quelles interfaces existent, quelles adresses elles portent, comment le trafic est routé, et quels réseaux libvirt sont définis.

Fenêtre de terminal
# Interfaces réseau
ip -br link
ip -br addr
# Bridges
bridge link show
# Routes
ip route
# Réseaux libvirt
virsh net-list --all

À lancer dans cet ordre, du plus général au plus précis : le routage doit valoir 1, les règles de traduction doivent exister, dnsmasq doit tourner, et les baux confirment que le DHCP a bien répondu.

Fenêtre de terminal
# ip_forward
cat /proc/sys/net/ipv4/ip_forward
# Règles NAT
sudo iptables -t nat -L -n -v 2>/dev/null | head -20
sudo nft list chain ip nat POSTROUTING 2>/dev/null
# Processus dnsmasq
ps aux | grep dnsmasq
# Leases DHCP
virsh net-dhcp-leases default
cat /var/lib/libvirt/dnsmasq/default.leases

Trois vérifications suffisent : l'interface physique figure bien parmi les membres du bridge, l'IP est portée par br0 et non par l'interface physique, et le STP ne bloque pas le port. Un stp_state à 1 explique une trentaine de secondes de silence après chaque démarrage.

Fenêtre de terminal
# Membres du bridge
bridge link show br0
# Adresses sur le bridge
ip addr show br0
# STP
cat /sys/class/net/br0/bridge/stp_state

  1. Séparez Host vs Guest : la moitié des pannes sont côté VM (firewall, DHCP client, DNS)
  2. virsh domifaddr peut mentir : utilisez --source lease/arp/agent ou virsh net-dhcp-leases
  3. virtnetworkd remplace libvirtd pour le réseau sur les distros modernes
  4. Les règles firewall sont régénérées au redémarrage du réseau libvirt
  5. Docker peut interférer : vérifiez ip_forward et l'ordre des règles NAT
  6. "virbr0 in use" = bridge orphelin ou collision de réseau
  7. Le firewall du guest est souvent le coupable quand "tout est OK côté hôte"
  8. Testez toujours : ping gateway → ping 8.8.8.8 → nslookup

Les questions ci-dessous reprennent les points sur lesquels les lecteurs reviennent le plus après un premier dépannage, notamment le choix entre NAT et bridge et le comportement de virsh domifaddr.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn