This is Part 1 of the AD DFIR Lab series. We start from a freshly provisioned Hetzner dedicated server and end with Proxmox VE installed and ready to create virtual machines.

The server

For this lab we use a Hetzner AX41-NVMe from the Server Auction (refurbished servers):

Component Specification
CPU AMD Ryzen 5 3600 (6 cores, 12 threads)
RAM 64 GB DDR4 (Non-ECC)
Storage 2x 512 GB NVMe
Network 1 Gbps, 20 TB/month included
Location Helsinki, Finland
Cost 38.40 EUR/month (VAT included)

64 GB of RAM is enough to run all 9 lab VMs (35 GB allocated to VMs + 4 GB for Proxmox/ZFS), and the two NVMe drives allow us to set up RAID1 for redundancy.

Before you start

Every phase below runs a script from this series’ repo, which lives at /root/lab on the server. So get it there first — clone it from the rescue system, and again on the freshly installed system after Part 1 (Part 1 reinstalls the OS, which wipes the disk):

git clone https://github.com/jupyterj0nes/ad-dfir-lab /root/lab

The scripts read everything server-specific — your public IP block, the uplink NIC, the ZFS pool name, the rescue image — from config/lab.env, so you don’t have to dig through the code to adapt them. Copy the example, fill in your values, and every 0X-*.sh picks them up:

cp /root/lab/config/lab.env.example /root/lab/config/lab.env
# then edit config/lab.env: set SERVER_SUBNET and anything that differs from the defaults

The IPs you’ll see below (203.0.113.x) are placeholders; yours will be your server’s real ones.

Step 1: Rescue System

When Hetzner delivers the server, the first step is to boot into the Rescue System — a minimal Linux loaded into RAM that lets you install the operating system on the disks.

From the Hetzner Robot panel:

  1. Rescue → Activate Linux 64-bit → Note the root password
  2. Reset → Hardware Reset

In 2-3 minutes the server boots into rescue and you can connect via SSH:

ssh root@203.0.113.229

What we see upon connecting:

CPU1: AMD Ryzen 5 3600 6-Core Processor (Cores 12)
Memory:  64244 MB (Non-ECC)
Disk /dev/nvme0n1: 512 GB (=> 476 GiB)
Disk /dev/nvme1n1: 512 GB (=> 476 GiB)

Two virgin NVMe drives, no partition table.

Step 2: Install Debian with installimage

Hetzner provides installimage, a proprietary tool that installs the operating system unattended. Our script 01-install-proxmox.sh automates the entire process.

Disk detection

First, we detect the NVMe disks correctly. The rescue system has a loop device that can confuse detection:

DISKS=($(lsblk -dnpo NAME,TYPE | grep disk | grep -E "nvme|sd" | cut -d" " -f1 | head -2))
# Result: /dev/nvme0n1 /dev/nvme1n1

installimage configuration

We generate the configuration for installimage:

DRIVE1 /dev/nvme0n1
DRIVE2 /dev/nvme1n1
SWRAID 1
SWRAIDLEVEL 1
BOOTLOADER grub
HOSTNAME proxmox-lab
PART /boot ext3 1024M
PART lvm vg0 all

LV vg0 root / ext4 50G
LV vg0 swap swap swap 8G

IMAGE /root/.oldroot/nfs/images/Debian-1213-bookworm-amd64-base.tar.gz

Key points:

  • Software RAID1 across both NVMe drives — if one fails, you don’t lose the lab
  • LVM with a 50 GB root volume and 8 GB swap
  • The remaining space in vg0 stays free for ZFS (we’ll use it later)
  • Debian 12 Bookworm as the base — required by Proxmox 8
/root/.oldroot/nfs/install/installimage -a -c /tmp/installimage-config

Installation takes about 70 seconds. Then, reboot.

Step 3: Install Proxmox VE

After rebooting, we’re on a clean Debian 12. The script 02-configure-proxmox.sh installs Proxmox and configures everything needed.

Repository and packages

# Add Proxmox repo (community, no subscription)
echo "deb [arch=amd64] http://download.proxmox.com/debian/pve bookworm pve-no-subscription" \
    > /etc/apt/sources.list.d/pve.list

# Install Proxmox VE
apt-get install -y proxmox-ve postfix open-iscsi chrony

Proxmox installation takes about 3 minutes and includes the Proxmox kernel (6.8.12-20-pve), the web interface, and all virtualization tools.

VLAN-aware bridge

A VLAN splits one physical network into several isolated virtual ones, each identified by a numeric tag. For VMs to have networking we need a bridge; Proxmox uses vmbr0 as the main one, and we configure it as VLAN-aware so that single bridge can carry several VLANs — one segment per VM group:

auto vmbr0
iface vmbr0 inet static
    address 203.0.113.229/26
    gateway 203.0.113.193
    bridge-ports enp35s0
    bridge-stp off
    bridge-fd 0
    bridge-vlan-aware yes
    bridge-vids 2-4094

With bridge-vlan-aware yes, each VM can have its own VLAN tag. Domain VMs will go on VLAN 10, and Kali on VLAN 20.

ZFS for VM storage

ZFS is a combined filesystem and volume manager: it stores the VM disks and, because it works copy-on-write, can freeze or roll back their exact state in an instant. That’s why it gives us snapshots, compression, and the ability to revert the entire lab to a clean state in seconds. We use the free LVM space:

# Create logical volume with 90% of free space
lvcreate -l 90%FREE -n zfsdata vg0

# Create ZFS pool with compression
zpool create -f vmstore /dev/vg0/zfsdata
zfs set compression=lz4 vmstore

# Create datasets
zfs create -p vmstore/images
zfs create -p vmstore/templates

Then register the pool in Proxmox:

pvesm add zfspool local-zfs -pool vmstore/images -content images,rootdir

Additional tools

apt-get install -y vim htop tmux wget curl git unzip \
    wireguard-tools qemu-guest-agent libguestfs-tools \
    python3 python3-pip python3-venv jq

After a final reboot into the Proxmox kernel, we have:

pve-manager/8.4.18 (running kernel: 6.8.12-20-pve)

Storage:
  local        dir       51 GB (ISOs, snippets)
  local-zfs    zfspool  364 GB (VM disks)

Step 4: Download ISOs

The script 03-download-isos.sh downloads all required ISOs. Linux ISOs download directly, and for Windows we discovered that direct Microsoft CDN URLs work with wget:

ISO Size Download
Windows Server 2019 Eval 5.0 GB Direct Microsoft CDN
Windows Server 2016 Eval 6.5 GB Direct Microsoft CDN
Windows 10 Enterprise Eval 4.5 GB Direct Microsoft CDN
Kali Linux 2026.1 4.5 GB cdimage.kali.org
Ubuntu 22.04 Server 2.0 GB releases.ubuntu.com
pfSense CE 835 MB atxfiles.netgate.com
VirtIO drivers 754 MB fedorapeople.org
# Windows ISOs download with browser user-agent
wget --user-agent="Mozilla/5.0" -O windows-server-2019.iso \
    "https://software-download.microsoft.com/download/pr/17763.737.190906-2324.rs5_release_svc_refresh_SERVER_EVAL_x64FRE_en-us_1.iso"

Total: 24 GB of ISOs, downloaded in about 10 minutes thanks to Hetzner’s bandwidth.

Resumable scripts

An important detail about the automation: all scripts use a checkpoint system (lib-state.sh) that allows:

  • Resuming from the last completed step if something fails
  • Skipping already completed steps on re-run
  • Inspecting each phase’s state
$ bash 02-configure-proxmox.sh

=== 02-configure-proxmox ===

  [SKIP] Setting hostname to proxmox-lab (already done)
  [SKIP] Adding Proxmox VE repository (already done)
  [SKIP] Installing Proxmox VE (already done)
  [10]   Creating ZFS pool for VM storage...
  [OK]   Creating ZFS pool for VM storage (0s)
  ...

If the SSH connection drops or there’s a temporary error, just re-run the script and it continues where it left off.

Result

At the end of this phase we have:

  • Proxmox VE 8.4 running on Debian 12
  • ZFS with 364 GB available for VMs (lz4 compression)
  • Bridge vmbr0 VLAN-aware, ready for network segmentation
  • 7 ISOs downloaded and ready to create VMs
  • Web interface accessible at https://IP:8006
  • Everything automated and documented in 3 resumable scripts

Total time: about 20 minutes from blank server.


Next: Part 2 — The Seven Kingdoms: Deploying Windows VMs with Unattended Install

Previous: Introduction — The Iron Throne of DFIR