Skip to content

Simulating Enterprise Network Infrastructures with KVM/QEMU & virt-manager

Created: 2026-11-05  Β·  Last updated: 2026-11-06  Β·  Nejat Hakan

Designing, hardening, and troubleshooting multi-zone network architecturesβ€”complete with perimeter firewalls, isolated DMZs, internal LANs, authoritative DNS servers, and reverse proxiesβ€”requires a realistic testing ground. Building such a lab with physical switches, routers, and rack servers is expensive, noisy, and rigid.

By combining the Linux Kernel-based Virtual Machine (KVM), QEMU, libvirt, and virt-manager, you can transform a single Linux workstation or server into a Type-1 enterprise virtualization lab capable of running complete multi-subnet network topologies at near-native hardware speed.


1. The Linux Virtualization Architecture (KVM + QEMU + libvirt)

A frequent point of confusion for newcomers is how KVM, QEMU, libvirt, and virt-manager relate to each other. Rather than competing tools, they form a clean 4-layer virtualization stack:

%%{init: {'flowchart': {'wrappingWidth': 600}}}%%
flowchart TB
    L4["πŸ–₯️ Layer 4: Management Clients & UIs<br/>virt-manager (GUI) &nbsp;Β·&nbsp; virsh / virt-install (CLI) &nbsp;Β·&nbsp; Cockpit (Web-UI)"]
    L3["βš™οΈ Layer 3: libvirtd Orchestration Daemon<br/>qemu:///system Β· XML Domains Β· Storage Pools Β· virbr0 Linux Bridges"]
    L2["πŸ“¦ Layer 2: QEMU Machine Emulator (User Space)<br/>qemu-system-x86_64 Processes Β· VirtIO Paravirtualization Β· UEFI (OVMF)"]
    L1["🐧 Layer 1: KVM Linux Kernel Hypervisor (/dev/kvm)<br/>kvm-intel / kvm-amd · Direct Hardware Virtualization of vCPU & vRAM"]
    HW["πŸ”© Bare-Metal Host Hardware<br/>Intel VT-x / AMD-V CPU Extensions & NVMe / SSD Storage"]

    L4 ==>|"UNIX RPC Socket: /run/libvirt/libvirt-sock"| L3
    L3 ==>|"Process Lifecycle & XML Generation"| L2
    L2 <==>|"ioctl(/dev/kvm) System Calls"| L1
    L1 <==>|"Ring -1 Root-Mode CPU Execution"| HW

The Four Core Components Explained

  • 1. KVM (kvm.ko) β€” The Hypervisor Core


    A mainline Linux kernel module that turns the Linux kernel itself into a Type-1 Bare-Metal Hypervisor. It exposes /dev/kvm to execute guest CPU instructions directly on physical Intel VT-x or AMD AMD-V silicon.

  • 2. QEMU β€” The Hardware & VirtIO Emulator


    While KVM handles CPU and RAM virtualization, QEMU runs as an unprivileged user-space process per VM, emulating motherboards, UEFI (OVMF), PCI buses, and high-speed paravirtualized VirtIO disks and network cards.

  • 3. libvirtd & virsh β€” The Control Plane


    A system daemon that manages VM lifecycles, declarative XML configurations, virtual Layer-2 switches (virbr*), dnsmasq DHCP/DNS instances, and storage pools.

  • 4. virt-manager β€” The Graphical Console


    A feature-rich desktop GUI client (with built-in SPICE / VNC graphical consoles, snapshot managers, and live CPU/RAM/Disk/Network telemetry graphs) talking to libvirtd locally or remotely over SSH.


2. Host Setup & Hardware Verification

Step 1: Pre-Flight Hardware Virtualization Check

Verify that Intel VT-x (vmx) or AMD AMD-V (svm) extensions are enabled in your BIOS/UEFI and that the /dev/kvm device node is active:

Terminal: Verify CPU Virtualization Flags & KVM Kernel Modules
# 1. Check CPU virtualization flags (look for VT-x or AMD-V)
lscpu | grep -i virtualization

# 2. Verify that kvm and kvm_intel / kvm_amd kernel modules are loaded
lsmod | grep kvm

# 3. Verify /dev/kvm device node permissions
ls -la /dev/kvm

Step 2: Install the Complete Virtualization Stack

Select your host Linux distribution below to install KVM, QEMU, libvirt, virt-manager, bridge-utils, and UEFI (OVMF) firmware support:

Terminal: Install KVM/QEMU Stack on Debian/Ubuntu/Mint
sudo apt update
sudo apt install -y qemu-kvm libvirt-daemon-system libvirt-clients \
  bridge-utils virt-manager virtinst ovmf swtpm spice-vdagent cpu-checker

# Grant your user permission to manage system VMs without sudo
sudo usermod -aG libvirt,kvm $(whoami)

# Verify KVM hardware acceleration readiness
kvm-ok
Terminal: Install KVM/QEMU Stack on Fedora/RHEL
sudo dnf install -y @virtualization virt-manager virt-install edk2-ovmf swtpm
sudo systemctl enable --now libvirtd
sudo usermod -aG libvirt,kvm $(whoami)
Terminal: Install KVM/QEMU Stack on Arch Linux
sudo pacman -Syu --needed qemu-full libvirt virt-manager virt-install \
  dnsmasq iptables-nft bridge-utils edk2-ovmf swtpm
sudo systemctl enable --now libvirtd
sudo usermod -aG libvirt,kvm $(whoami)
Terminal: Install KVM/QEMU Stack on openSUSE
sudo zypper install -y qemu-kvm libvirt virt-manager virt-install bridge-utils qemu-ovmf-x86_64
sudo systemctl enable --now libvirtd
sudo usermod -aG libvirt,kvm $(whoami)

Log Out or Run newgrp libvirt After Adding Groups!

Linux only evaluates group memberships at login. After running sudo usermod -aG libvirt,kvm $(whoami), either log out and back into your desktop session or run newgrp libvirt in your current shell before testing virsh list --all.

Step 3: Verify libvirtd & Default Network Status

Terminal: Verify Libvirt Daemon, Default Storage Pool & Default Network
# 1. Verify daemon status
sudo systemctl is-active libvirtd

# 2. Test unprivileged virsh access
virsh list --all

# 3. Verify the 'default' NAT network and storage pool are active
virsh net-list --all
virsh pool-list --all

3. Virtual Disk Architectures, Storage Pools & Snapshots

Virtual machines store their operating systems and data inside Storage Volumes carved out of Storage Pools (by default located at /var/lib/libvirt/images/).

Disk Image Formats: qcow2 vs. raw

Feature / Dimension qcow2 (QEMU Copy-On-Write v2) raw (Bit-for-Bit Image)
Provisioning Default Thin Provisioned (Grows dynamically on demand) Thick Provisioned (Reserves full size immediately)
Internal Snapshots βœ… Yes (Supports multi-branch disk & live RAM snapshots) ❌ No (Requires external LVM/ZFS/Btrfs snapshots)
Backing Files (Clones) βœ… Yes (Instant Copy-on-Write linked clones from a base image) ❌ No
Compression & AES Crypto βœ… Yes (zlib / zstd compression & LUKS encryption) ❌ No
I/O Performance Near-native (especially with VirtIO bus + preallocation=metadata) Maximum raw block throughput (zero metadata overhead)
Recommended Use Case Default for 99% of Lab & Server VMs High-IOPS Databases or LVM/Ceph block volumes

Engineering Deep-Dive: Two Real-World virt-manager Storage Quirks Explained

When creating custom storage pools and volumes in virt-manager, you will encounter two subtle behaviors:

  1. Why Relative Paths (~/kvm_storage_pool) Fail in virt-manager:
    When connected to QEMU/KVM (qemu:///system), the libvirtd daemon runs as root, not as your desktop user. If you type ~/kvm_storage_pool as a storage pool target path, libvirtd does not expand your user's tilde (~), resulting in phantom volumes that do not appear in your home directory. Always specify the full absolute path (e.g., /home/nejat/kvm_storage_pool)!
  2. Why virt-manager GUI qcow2 Volumes Sometimes Allocate Full Space vs. qemu-img CLI:
    When you run qemu-img create -f qcow2 disk.qcow2 5G in the terminal, QEMU creates a pure sparse file consuming only ~200 KiB on disk. By contrast, when creating a volume via the virt-manager GUI inside certain directory pools, libvirt invokes posix_fallocate() unless sparse allocation is explicitly enforced, immediately reserving the blocks on disk.

Hands-On Lab 1: Mastering qemu-img Creation, Inspection & Format Conversion

Use the qemu-img CLI tool to create thin-provisioned qcow2 disks, thick raw disks, and convert between formats (including VMware .vmdk or VirtualBox .vdi images):

Terminal: Create, Inspect, and Convert Virtual Disk Images
# 1. Create a temporary lab directory
mkdir -p ~/kvm_disk_lab && cd ~/kvm_disk_lab

# 2. Create a 5 GiB thin-provisioned (sparse) qcow2 image
qemu-img create -f qcow2 thin_disk.qcow2 5G

# 3. Inspect virtual size (5 GiB) vs. actual physical disk usage (~196 KiB!)
qemu-img info thin_disk.qcow2

# 4. Create a 1 GiB raw image and convert it into a compressed qcow2 image
qemu-img create -f raw thick_disk.raw 1G
qemu-img convert -c -f raw -O qcow2 thick_disk.raw converted_disk.qcow2
qemu-img info converted_disk.qcow2

# 5. Clean up lab files
cd ~ && rm -rf ~/kvm_disk_lab

4. Virtual Networking Modes in libvirt & KVM

To simulate realistic enterprise infrastructures, libvirt provides three core virtual networking modes using Linux kernel bridges (virbr*) and tap interfaces (vnet*):

%%{init: {'flowchart': {'wrappingWidth': 600, 'subGraphTitleMargin': {'top': 15, 'bottom': 15}}}}%%
flowchart TB
    subgraph TopLevel ["πŸ–₯️ Virtual Machines (KVM / QEMU Guest Tier)"]
        VM_NAT["πŸ–₯️ NAT VM<br/>(192.168.122.x)"]
        VM_ISO1["πŸ–₯️ Isolated VM A<br/>(10.0.1.10)"]
        VM_ISO2["πŸ–₯️ Isolated VM B<br/>(10.0.1.50)"]
        VM_BR["πŸ–₯️ LAN Bridge VM<br/>(192.168.1.x)"]
    end

    subgraph Bridges ["🐧 Linux KVM Host: libvirt Bridges & Kernel Switches"]
        direction TB
        B_NAT["1️⃣ virbr0 (NAT Bridge)<br/>192.168.122.1 / dnsmasq"]
        B_ISO["2️⃣ virbr1 (Isolated L2 Switch)<br/>Pure Bridge (Zero Routing)"]
        B_BR["3️⃣ br0 (Physical Host Bridge)<br/>Enslaved eth0 on LAN"]
    end

    VM_NAT <==>|"vnet0"| B_NAT
    VM_ISO1 <==>|"vnet1"| B_ISO
    VM_ISO2 <==>|"vnet2"| B_ISO
    VM_BR <==>|"vnet3"| B_BR

    NIC["🌐 Host Physical NIC (eth0)"]
    UPLINK(("🏠 Physical LAN Router & Internet<br/>(192.168.1.1)"))

    B_NAT ==>|"Host NAT / MASQUERADE"| NIC
    B_BR ==>|"Layer-2 Trunk"| NIC
    NIC <==> UPLINK

Comparing the 4 Virtual Network Topologies

Network Mode Outbound Internet? Inbound from Physical LAN? VM-to-VM & Host Access? Primary Lab Use Case
1. NAT (default / virbr0) βœ… Yes (via Host NAT) ❌ No (Unless Port-Forwarded) βœ… Yes (192.168.122.1) WAN Uplink for your virtual pfSense firewall or quick standalone VMs
2. Isolated (lan1-net, dmz1-net) ❌ No (Strictly Air-Gapped) ❌ No βœ… Yes (Same switch only) Internal LAN & DMZ segments routed exclusively through a virtual firewall
3. Host Bridge (br0) βœ… Yes (Direct via LAN) βœ… Yes (Gets LAN DHCP IP) βœ… Yes Exposing a production VM directly to your home/office physical network
4. Routed Mode βœ… Yes (With static routes) βœ… Yes (With static routes) βœ… Yes Advanced BGP/OSPF routing labs without NAT

5. Architecting Our Multi-Zone Enterprise Lab Topology

Now we combine everything to build a complete, realistic 3-Zone Enterprise Network Infrastructure inside KVM/QEMU.

Complete Network & IP Architecture Diagram

%%{init: {'flowchart': {'wrappingWidth': 600, 'subGraphTitleMargin': {'top': 15, 'bottom': 15}}}}%%
flowchart TB
    Internet(("🌍 Internet & Host NAT<br/>virbr0 (192.168.122.0/24)<br/>Gateway: 192.168.122.1"))

    Internet <==>|"WAN (vtnet0)<br/>DHCP: 192.168.122.x"| FW

    subgraph Firewall ["πŸ›‘οΈ Perimeter Router & Stateful Firewall"]
        FW["πŸ”₯ fw01 (pfSense CE)<br/>WAN: vtnet0 (DHCP)<br/>LAN: vtnet1 (10.0.1.1/24)<br/>DMZ: vtnet2 (10.0.2.1/24)"]
    end

    FW <==>|"LAN Interface (vtnet1)<br/>Gateway: 10.0.1.1"| SwitchLAN["πŸ–§ Isolated Switch: lan1-net<br/>Subnet: 10.0.1.0/24"]
    FW <==>|"DMZ Interface (vtnet2)<br/>Gateway: 10.0.2.1"| SwitchDMZ["πŸ–§ Isolated Switch: dmz1-net<br/>Subnet: 10.0.2.0/24"]

    subgraph ZoneLAN ["🏒 Internal Trusted LAN Zone (10.0.1.0/24)"]
        SRV01["πŸ—„οΈ srv01.yourlab.lan<br/>Ubuntu Server (BIND9 DNS)<br/>Static IP: 10.0.1.10/24"]
        CLIENT01["πŸ’» client01.yourlab.lan<br/>Linux Client + Wireshark<br/>Static/DHCP IP: 10.0.1.50/24"]
    end

    subgraph ZoneDMZ ["🌐 Demilitarized Zone / DMZ (10.0.2.0/24)"]
        DMZ01["πŸš€ dmz01.yourlab.lan<br/>Ubuntu Server (Nginx VHosts)<br/>Static IP: 10.0.2.20/24"]
    end

    SwitchLAN === SRV01 & CLIENT01
    SwitchDMZ === DMZ01

Master IP & VM Allocation Table

VM Name (libvirt) Hostname (FQDN) Role & Services vCPUs / RAM / Disk Network Interfaces & IP Addresses
fw01 fw01.yourlab.lan pfSense Stateful Firewall, NAT & DHCP 1 vCPU / 1 GB / 10 GB β€’ WAN (vtnet0): default (DHCP 192.168.122.x)
β€’ LAN (vtnet1): lan1-net (10.0.1.1/24)
β€’ DMZ (vtnet2): dmz1-net (10.0.2.1/24)
ubuntu-server-01 srv01.yourlab.lan
ns1.yourlab.lan
BIND9 Authoritative & Recursive DNS 2 vCPU / 2 GB / 20 GB β€’ LAN (enp1s0): lan1-net (10.0.1.10/24, GW 10.0.1.1)
client-vm-01 client01.yourlab.lan Admin Workstation, Browser & Wireshark 2 vCPU / 2 GB / 15 GB β€’ LAN (enp1s0): lan1-net (10.0.1.50/24, GW 10.0.1.1)
dmz-server-01 dmz01.yourlab.lan
app.yourlab.lan
Nginx Web Server (Multi-VHost) 1 vCPU / 1 GB / 15 GB β€’ DMZ (enp1s0): dmz1-net (10.0.2.20/24, GW 10.0.2.1)

Step 1: Create the Isolated Virtual Switches (lan1-net & dmz1-net)

Because pfSense (fw01) will act as the gateway (10.0.1.1 and 10.0.2.1) and DHCP server for both internal segments, we create lan1-net and dmz1-net as pure Layer-2 isolated switches without libvirt's own dnsmasq DHCP server (so libvirt doesn't steal the .1 gateway IPs from pfSense!):

Run these commands on your KVM host to create, start, and autostart both isolated virtual switches in 5 seconds:

Terminal on KVM Host: Define & Start Pure Layer-2 Virtual Switches
# 1. Define lan1-net (Pure Layer-2 virtual switch virbr1)
cat << 'EOF' > /tmp/lan1-net.xml
<network>
  <name>lan1-net</name>
  <bridge name='virbr1' stp='on' delay='0'/>
</network>
EOF

# 2. Define dmz1-net (Pure Layer-2 virtual switch virbr2)
cat << 'EOF' > /tmp/dmz1-net.xml
<network>
  <name>dmz1-net</name>
  <bridge name='virbr2' stp='on' delay='0'/>
</network>
EOF

# 3. Register, start, and enable autostart for both networks
virsh net-define /tmp/lan1-net.xml
virsh net-start lan1-net
virsh net-autostart lan1-net

virsh net-define /tmp/dmz1-net.xml
virsh net-start dmz1-net
virsh net-autostart dmz1-net

rm -f /tmp/lan1-net.xml /tmp/dmz1-net.xml
virsh net-list --all
  1. Open virt-manager β†’ Edit β†’ Connection Details β†’ Virtual Networks tab.
  2. Click + (Add Network):
  3. Name: lan1-net
  4. Mode: Isolated network
  5. (If pfSense will hold 10.0.1.1 and run DHCP, uncheck IPv4 DHCP or let pfSense manage the subnet).
  6. Repeat for dmz1-net.

Step 2: Configure Static IPs on Ubuntu Server VMs (Modern Netplan Syntax)

Deprecation Notice: Netplan gateway4 β†’ routes

On Ubuntu 22.04 LTS and 24.04 LTS, the old gateway4: 10.0.1.1 key is deprecated. Always use the modern routes: - to: default via: ... block shown below!

/etc/netplan/00-installer-config.yaml on srv01
network:
  version: 2
  renderer: networkd
  ethernets:
    enp1s0:
      dhcp4: false
      addresses:
        - 10.0.1.10/24
      routes:
        - to: default
          via: 10.0.1.1
      nameservers:
        addresses: [127.0.0.1, 10.0.1.1]
        search: [yourlab.lan]
Apply cleanly with restricted permissions:
sudo chmod 600 /etc/netplan/00-installer-config.yaml
sudo netplan apply

/etc/netplan/00-installer-config.yaml on client01
network:
  version: 2
  renderer: networkd
  ethernets:
    enp1s0:
      dhcp4: false
      addresses:
        - 10.0.1.50/24
      routes:
        - to: default
          via: 10.0.1.1
      nameservers:
        addresses: [10.0.1.10, 10.0.1.1]
        search: [yourlab.lan]
/etc/netplan/00-installer-config.yaml on dmz01
network:
  version: 2
  renderer: networkd
  ethernets:
    enp1s0:
      dhcp4: false
      addresses:
        - 10.0.2.20/24
      routes:
        - to: default
          via: 10.0.2.1
      nameservers:
        addresses: [10.0.1.10, 10.0.2.1]
        search: [yourlab.lan]

6. Deploying the Perimeter Firewall (pfSense on fw01)

Step 1: Create the 3-NIC fw01 VM in virt-manager

  1. Download the pfSense CE AMD64 ISO (pfSense-CE-*-RELEASE-amd64.iso.gz), extract it with gunzip, and create a new VM named fw01 (OS type: FreeBSD 14.x, 1024 MB RAM, 1 vCPU, 10 GB VirtIO qcow2 disk).
  2. Check "Customize configuration before install" and configure three VirtIO NICs:
  3. NIC 1 (vtnet0 β€” WAN): Virtual network 'default': NAT
  4. NIC 2 (vtnet1 β€” LAN): Virtual network 'lan1-net': Isolated
  5. NIC 3 (vtnet2 β€” OPT1/DMZ): Virtual network 'dmz1-net': Isolated
  6. Complete the UFS/ZFS guided installer, disconnect the ISO image, and reboot into the pfSense console menu.

Step 2: Console Interface & IP Assignment

From the pfSense text console menu, select Option 1 (Assign Interfaces): * WAN: vtnet0 * LAN: vtnet1 * OPT1: vtnet2

Then select Option 2 (Set interface(s) IP address): 1. LAN (vtnet1): Static IPv4 10.0.1.1/24, enable DHCP range 10.0.1.100 – 10.0.1.200. 2. OPT1 (vtnet2): Static IPv4 10.0.2.1/24, enable DHCP range 10.0.2.100 – 10.0.2.200.

Crucial Lab Setting: Uncheck 'Block RFC1918 Private Networks' on WAN!

Because fw01's WAN interface (vtnet0) connects to libvirt's default NAT network (192.168.122.0/24β€”which is itself an RFC 1918 private subnet!), you must uncheck "Block private networks and loopback addresses" under Interfaces β†’ WAN in the pfSense WebGUI (https://10.0.1.1).

Step 3: Zero-Trust Firewall Policy Matrix (LAN vs. DMZ)

In pfSense (Firewall β†’ Rules), rules are evaluated top-to-bottom on the ingress interface (first match wins; implicit deny at the bottom):

Interface Tab Action Protocol Source Destination Port(s) Purpose & Security Logic
LAN 🟒 Pass Any LAN net Any Any Allows trusted LAN admins/clients to reach DMZ, WAN, and internal services.
DMZ 🟒 Pass TCP/UDP DMZ net 10.0.1.10 (srv01) 53 (DNS) Allows DMZ servers to query the internal BIND9 DNS server only.
DMZ πŸ”΄ Block Any DMZ net LAN net (10.0.1.0/24) Any Zero-Trust Isolation: Prevents a compromised DMZ server from pivoting into the LAN!
DMZ 🟒 Pass TCP/UDP DMZ net Any (Internet) 80, 443, 123 Allows DMZ servers to pull apt OS updates and sync NTP time.

7. Authoritative & Recursive Internal DNS Server (BIND9 on srv01)

To resolve internal FQDNs (srv01.yourlab.lan, dmz01.yourlab.lan, app.yourlab.lan) and reverse PTR lookups (10.0.1.x and 10.0.2.x), we deploy BIND9 on srv01 (10.0.1.10).

Step 1: Install BIND9 & Configure Options

Terminal on srv01 (10.0.1.10): Install BIND9 & Utilities
sudo apt update
sudo apt install -y bind9 bind9utils bind9-doc dnsutils

Configure /etc/bind/named.conf.options with strict Access Control Lists (internal-nets) so only LAN1 and DMZ1 can query your resolver:

/etc/bind/named.conf.options
acl "internal-nets" {
    127.0.0.0/8;
    10.0.1.0/24;
    10.0.2.0/24;
};

options {
    directory "/var/cache/bind";

    listen-on port 53 { 127.0.0.1; 10.0.1.10; };
    listen-on-v6 { none; };

    allow-query { internal-nets; };
    allow-recursion { internal-nets; };
    recursion yes;

    forwarders {
        10.0.1.1;
        1.1.1.1;
    };
    forward first;

    dnssec-validation auto;
};

Step 2: Declare Forward & Reverse Zones (named.conf.local)

/etc/bind/named.conf.local
zone "yourlab.lan" {
    type master;
    file "/etc/bind/zones/db.yourlab.lan";
    allow-update { none; };
};

zone "1.0.10.in-addr.arpa" {
    type master;
    file "/etc/bind/zones/db.10.0.1";
    allow-update { none; };
};

zone "2.0.10.in-addr.arpa" {
    type master;
    file "/etc/bind/zones/db.10.0.2";
    allow-update { none; };
};

Step 3: Populate Zone Files & Validate Syntax

/etc/bind/zones/db.yourlab.lan
$TTL    3600
@       IN      SOA     ns1.yourlab.lan. admin.yourlab.lan. (
                            2026110501  ; Serial (YYYYMMDDSS)
                            7200        ; Refresh
                            3600        ; Retry
                            1209600     ; Expire
                            3600 )      ; Negative Cache TTL
;
@            IN      NS      ns1.yourlab.lan.

; --- LAN1 Hosts (10.0.1.0/24) ---
ns1          IN      A       10.0.1.10
srv01        IN      A       10.0.1.10
fw01         IN      A       10.0.1.1
client01     IN      A       10.0.1.50

; --- DMZ1 Hosts (10.0.2.0/24) ---
fw01-dmz     IN      A       10.0.2.1
dmz01        IN      A       10.0.2.20
app          IN      A       10.0.2.20

; --- Aliases (CNAME) ---
www          IN      CNAME   dmz01.yourlab.lan.
firewall     IN      CNAME   fw01.yourlab.lan.
/etc/bind/zones/db.10.0.1
$TTL    3600
@       IN      SOA     ns1.yourlab.lan. admin.yourlab.lan. (
                            2026110501  ; Serial
                            7200
                            3600
                            1209600
                            3600 )
;
@       IN      NS      ns1.yourlab.lan.

1       IN      PTR     fw01.yourlab.lan.
10      IN      PTR     srv01.yourlab.lan.
50      IN      PTR     client01.yourlab.lan.
/etc/bind/zones/db.10.0.2
$TTL    3600
@       IN      SOA     ns1.yourlab.lan. admin.yourlab.lan. (
                            2026110501  ; Serial
                            7200
                            3600
                            1209600
                            3600 )
;
@       IN      NS      ns1.yourlab.lan.

1       IN      PTR     fw01-dmz.yourlab.lan.
20      IN      PTR     dmz01.yourlab.lan.

Validate all syntax before restarting bind9:

Terminal on srv01: Pre-Flight Validation & Service Reload
sudo mkdir -p /etc/bind/zones
sudo chown -R bind:bind /etc/bind/zones
sudo chmod 755 /etc/bind/zones && sudo chmod 644 /etc/bind/zones/db.*

# Syntax validation (must return 0 errors!)
sudo named-checkconf
sudo named-checkzone yourlab.lan /etc/bind/zones/db.yourlab.lan
sudo named-checkzone 1.0.10.in-addr.arpa /etc/bind/zones/db.10.0.1
sudo named-checkzone 2.0.10.in-addr.arpa /etc/bind/zones/db.10.0.2

sudo systemctl restart bind9

8. Deploying Name-Based Virtual Hosts with Nginx in the DMZ (dmz01)

On dmz01 (10.0.2.20), we deploy Nginx and configure two separate Virtual Hosts (dmz01.yourlab.lan and app.yourlab.lan) sharing the single IP 10.0.2.20 via HTTP Host header routing:

Terminal on dmz01 (10.0.2.20): Install Nginx & Create Two Virtual Hosts
# 1. Install Nginx
sudo apt update && sudo apt install -y nginx

# 2. Create document roots and landing pages
sudo mkdir -p /var/www/dmz01_yourlab_lan/html /var/www/app_yourlab_lan/html
echo "<h1>Welcome to dmz01.yourlab.lan (10.0.2.20)</h1>" | sudo tee /var/www/dmz01_yourlab_lan/html/index.html
echo "<h1>Welcome to app.yourlab.lan (10.0.2.20)</h1>"   | sudo tee /var/www/app_yourlab_lan/html/index.html
sudo chown -R www-data:www-data /var/www/dmz01_yourlab_lan /var/www/app_yourlab_lan

# 3. Create Virtual Host configs
cat << 'EOF' | sudo tee /etc/nginx/sites-available/dmz01.yourlab.lan
server {
    listen 80 default_server;
    server_name dmz01.yourlab.lan www.yourlab.lan;
    root /var/www/dmz01_yourlab_lan/html;
    index index.html;
    location / { try_files $uri $uri/ =404; }
}
EOF

cat << 'EOF' | sudo tee /etc/nginx/sites-available/app.yourlab.lan
server {
    listen 80;
    server_name app.yourlab.lan;
    root /var/www/app_yourlab_lan/html;
    index index.html;
    location / { try_files $uri $uri/ =404; }
}
EOF

# 4. Enable sites, remove default, test config, and reload
sudo ln -sf /etc/nginx/sites-available/dmz01.yourlab.lan /etc/nginx/sites-enabled/
sudo ln -sf /etc/nginx/sites-available/app.yourlab.lan /etc/nginx/sites-enabled/
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t && sudo systemctl reload nginx

9. Packet Inspection & Protocol Analysis with Wireshark

Because lan1-net (virbr1) and dmz1-net (virbr2) are Linux bridge interfaces directly on your KVM Host, you can launch Wireshark on your host machine and capture every single packet traversing your virtual network without installing a GUI inside your server VMs!

End-to-End Packet Flow: DNS Lookup + TCP 3-Way Handshake + HTTP GET

When client01 (10.0.1.50) runs curl http://app.yourlab.lan, Wireshark captures the exact multi-layer sequence across virbr1 and virbr2:

%%{init: {'flowchart': {'wrappingWidth': 600, 'subGraphTitleMargin': {'top': 15, 'bottom': 15}}}}%%
flowchart TB
    subgraph Phase1 ["1️⃣ Phase 1: UDP Port 53 β€” DNS Resolution (on virbr1)"]
        D1["client01 (10.0.1.50) β†’ srv01<br/>DNS Query: A app.yourlab.lan"]
        D2["srv01 (10.0.1.10) β†’ client01<br/>DNS Response: A 10.0.2.20"]
        D1 ==> D2
    end

    subgraph Phase2 ["2️⃣ Phase 2: TCP Port 80 β€” Three-Way Handshake (Routed via fw01)"]
        T1["client01 β†’ dmz01:80<br/>TCP Flags: [SYN] Seq=0"]
        T2["dmz01:80 β†’ client01<br/>TCP Flags: [SYN, ACK] Seq=0 Ack=1"]
        T3["client01 β†’ dmz01:80<br/>TCP Flags: [ACK] Seq=1 Ack=1"]
        T1 ==> T2 ==> T3
    end

    subgraph Phase3 ["3️⃣ Phase 3: HTTP Layer-7 Payload & Graceful Teardown"]
        H1["client01 β†’ dmz01<br/>HTTP GET / (Host: app.yourlab.lan)"]
        H2["dmz01 β†’ client01<br/>HTTP/1.1 200 OK (Welcome Page)"]
        H3["[FIN, ACK] ↔ [FIN, ACK]<br/>(Connection Closed)"]
        H1 ==> H2 ==> H3
    end

    Phase1 ==> Phase2 ==> Phase3

Essential Wireshark Display Filters for Lab Debugging

Display Filter Expression What It Isolates
dns All DNS queries and responses (verify A and PTR lookups).
icmp All ping Echo Requests, Echo Replies, and ICMP Unreachable errors.
ip.addr == 10.0.1.50 && ip.addr == 10.0.2.20 Only packets flowing between client01 (LAN) and dmz01 (DMZ).
tcp.flags.syn == 1 && tcp.flags.ack == 0 Initial TCP [SYN] connection attempts (spot blocked firewall ports!).
http.host == "app.yourlab.lan" HTTP requests specifically targeting the app.yourlab.lan virtual host.
tcp.analysis.retransmission Highlights packet loss or MTU mismatches across virtual bridges.

Hands-On Lab 2: Live Verification & DNS Cache Flush from client01

Run these commands on client01 (10.0.1.50) while watching Wireshark on your KVM host's virbr1 interface:

Terminal on client01: Flush DNS Cache & Test Full Topology
# 1. Flush systemd-resolved DNS cache (Modern Debian 12 / Ubuntu 24.04 command)
sudo resolvectl flush-caches

# 2. Test Forward & Reverse DNS against srv01 (10.0.1.10)
dig app.yourlab.lan +short
dig -x 10.0.2.20 +short

# 3. Test HTTP Virtual Host Routing across the pfSense Firewall into DMZ1
curl -i http://dmz01.yourlab.lan
curl -i http://app.yourlab.lan

10. Continue Your Linux Networking Journey

Explore the companion guides in the Networking series to deepen your command-line and routing mastery:

  • Networking Basics


    Master IPv4/IPv6 addressing, subnet masks, gateways, DNS fundamentals, and essential CLI tools (ip, ping, ss).

    Basics: Getting Connected

  • Intermediate Configuration & Services


    Configure static interfaces, DHCP, SSH hardening, firewall rules (ufw / nftables), and network services.

    Intermediate Networking

  • Advanced Virtualization & Routing


    Dive into Linux kernel routing tables, VLAN tagging (802.1Q), network namespaces, policy routing, and traffic control (tc).

    Advanced Routing & VLANs

  • Container Network Isolation


    See how Linux network namespaces and Netavark power zero-trust microservice segmentation in Rootless Podman.

    Podman Networking Guide