Three phases. Bare-metal hardware to a running, segmented network with services. Every step explained in plain English. Every acronym spelled out the first time it's used.
Imagine you have one big metal computer (a "server") that you want to turn into five smaller fake computers inside it — each pretending to be its own machine, each doing a different job. They all share the same CPU, RAM, and disks, but they're isolated from each other. That's virtualization, and the software that does it is called a hypervisor.
Then you want those five fake computers to talk to each other through three different "rooms" with locked doors between them — so even if a stranger sneaks into one room, they can't reach the others. That's network segmentation, and we do it with virtual switches and a firewall.
Then on top of all that, we install real services: a website (web server), a database, a Windows domain controller, a Linux desktop client. So you can prove the whole thing works the way a real small company's network does.
This is what every IT department in the world does at scale. Your future job will involve some version of these same skills: hypervisor administration, network design, firewall configuration, server roles, client testing. The hardware you're using is real enterprise gear. The software is real production software. You're not playing a simulation — you're building a tiny version of the real thing.
Total time, if you've never done it before: about 25–35 hours of focused work, spread over 3–4 weeks.
If you've never set up a network before, the next 5 paragraphs will be the most important reading of your capstone. Don't skip.
A number like 192.168.0.20. It's the "house address" of one device on the network. Two devices can't have the same IP at the same time, just like two houses can't have the same address.
A group of houses on the same street. Written like 192.168.0.0/24, where the /24 means the first 24 bits identify the street and the last 8 bits identify the house number — so houses can range from 192.168.0.1 to 192.168.0.254 on this street. Devices on the same subnet can talk directly. Devices on different subnets need a router to forward their messages.
The IP address of the router on your subnet. When your computer wants to send something to a device that's NOT on its street, it sends it to the gateway and says "you handle it." For our lab, when a Linux server at 192.168.0.20 wants to reach Google, it sends the packet to 192.168.0.1 (the gateway, which is the firewall pfSense) and pfSense forwards it out to the internet.
The phone book of the internet. When you type www.google.com into your browser, your computer asks a DNS server "what's the IP for that name?" and the server replies with something like 142.251.157.119. Then your browser uses that IP to make the actual connection. In our lab, pfSense runs a DNS server (Unbound) that does this for all the VMs.
A service that hands out IP addresses automatically. When a new device joins a network, it shouts "anyone got an IP for me?" and the DHCP server replies with "use 192.168.0.157, and your gateway is 192.168.0.1, and your DNS is 192.168.0.1." Without DHCP, you'd have to manually configure every device. In our lab, pfSense runs DHCP for both internal subnets.
The trick that lets many internal devices share one external IP. Your home router uses it: all your phones and laptops have private addresses like 192.168.x.x, but to the internet they all look like one address (your ISP-assigned public IP). The router rewrites the source address as packets leave and rewrites the destination as replies come back. pfSense does NAT for our lab.
A network "buffer area" — an isolated subnet for things that need to face the outside world but shouldn't have direct access to your internal stuff. In our lab the jumpbox lives in the DMZ. If a hacker breaks into the jumpbox, the firewall still stops them from reaching the LAN.
Different names for the same idea: a virtual network "switch" inside a hypervisor that connects virtual machines together. In Proxmox they're called bridges (with names like vmbr0, vmbr1, vmbr2). Each bridge is a separate isolated network unless a router (like pfSense) connects them.
Bookmark this section. Whenever a step uses an acronym, it's defined here.
/24 after an IP. Tells you how big the subnet is. /24 = 256 addresses, /16 = 65,536, /8 = 16 million.google.com into IP addresses.winsrv.capstone.local is an FQDN; just winsrv is a hostname.ping. Sends a small "are you there?" packet and waits for a "yes" reply.192.168.0.1), IPv6 = 128-bit (e.g. fe80::1).BC:24:11:02:24:2B. Unique per NIC.10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16. Routers won't forward these to the public internet.ufw allow 22 instead of long iptables incantations.https://example.com/about.Open the server, photograph every component, and record specs (CPU, RAM, disks, NICs, PSUs). Without this you can't ask the instructor smart questions or order replacement parts.
HMT41GR7BFR4A-PB on a SK Hynix RAM module is real.Boot the server into its BIOS / UEFI firmware (the menus that appear before any operating system loads) and configure it for the install we're about to do.
Combine the server's three physical hard disks into one big logical drive using RAID 5 — Redundant Array of Independent Disks, level 5. With three 1 TB drives in RAID 5, you get ~2 TB of usable space and can survive any one drive failing.
Boot from a USB stick that has the Proxmox installer, walk through the screens, and install Proxmox VE 8.2 onto the RAID array.
https://www.proxmox.com/en/downloads./dev/sda or similar at ~2 TB). File system: ext4.tctmachine.local (or whatever your team chose)10.10.10.10/16 (your school-LAN-assigned address)10.10.10.1 (your school's router)1.1.1.1 (Cloudflare's public DNS — works anywhere)https://10.10.10.10:8006 as the URL to manage it from. From your laptop browser, that URL opens the Proxmox login page.nano /etc/network/interfaces to fix the IP, gateway, DNS.Open the Proxmox web interface from your laptop, log in, and confirm everything looks normal.
https://10.10.10.10:8006 (replace with your IP if different).root, Password = whatever you set, Realm PAM.tctmachine).root@10.10.10.10 from a Linux/Mac terminal and run systemctl status pveproxy pvedaemon.PAM (the Linux user database), not "Proxmox VE auth server." If you forgot the password, you'll need to boot into single-user mode from the server's physical console and reset it.Cmd+Shift+R).Before moving on, you should have:
https://10.10.10.10:8006 from your laptoproot, dashboard shows greenNow you have a hypervisor. Empty, but ready. Phase 2 fills it with virtual machines.
Before clicking anything, write down what addresses you'll use. This 10-minute step prevents 10 hours of pain.
| Zone | Subnet | Purpose | Gateway |
|---|---|---|---|
| School LAN (vmbr0) | 10.10.0.0/16 | Where Proxmox lives + WAN side of pfSense | School router (e.g. 10.10.10.1) |
| DMZ (vmbr1) | 172.16.0.0/24 | Jumpbox lives here · public-facing tier | 172.16.0.1 (pfSense) |
| LAN (vmbr2) | 192.168.0.0/24 | WinSrv + LinuxServer + Linux Desktop · internal tier | 192.168.0.1 (pfSense) |
| VM | IP | Bridge |
|---|---|---|
| Proxmox host | 10.10.10.10 | vmbr0 (physical NIC) |
| pfSense (firewall) | WAN 10.10.110.10 · DMZ 172.16.0.1 · LAN 192.168.0.1 | vmbr0 + vmbr1 + vmbr2 |
| Jumpbox | 172.16.0.100 via DHCP | vmbr1 |
| Windows Server (WinSrv) | 192.168.0.15 static | vmbr2 |
| Linux Server (LinuxServer) | 192.168.0.20 static | vmbr2 |
| Linux Desktop (Linux-Ubuntu) | 192.168.0.25 static | vmbr2 |
Static IPs are below the DHCP pool (which is .100–.200) — so no lease ever conflicts.
Inside the Proxmox host, create two new virtual switches (called "bridges"): one for the DMZ, one for the LAN. vmbr0 already exists from the Proxmox install.
tctmachine) → top tab >_ Shell. A web terminal opens.nano /etc/network/interfaces
Add these two blocks at the bottom (don't touch vmbr0):
auto vmbr1
iface vmbr1 inet static
address 172.16.0.10
netmask 255.255.255.0
bridge_ports none
bridge_stp off
bridge_fd 0
auto vmbr2
iface vmbr2 inet static
address 192.168.0.10
netmask 255.255.255.0
bridge_ports none
bridge_stp off
bridge_fd 0
Save (Ctrl+O Enter) and exit (Ctrl+X). Reload networking:
ifreload -a ip -br addr
ip -br addr shows three lines for vmbr0, vmbr1, vmbr2 — all in state UP with the correct IPs.systemctl restart networking. Pause critical workloads first; this can briefly drop your SSH session.nano may have introduced typos. Run cat /etc/network/interfaces and check spelling. bridge_ports not bridgeports.vmbr0. Use the physical console: log in as root, fix the file, restart networking.Get the operating-system installer disks (ISO files) onto Proxmox so VMs can boot from them. ISOs needed:
netgate-installer-v1.1.1-RELEASE-amd64.iso)ubuntu-24.04-live-server-amd64.iso or 26.04) — used for jumpbox + LinuxServerubuntu-24.04.4-desktop-amd64.iso) — used for Linux-Ubuntu clientBuild the most important VM: the firewall/router that will route traffic between all our subnets and out to the internet.
103, Name PFsense2.vmbr0, model VirtIO (paravirtualized).vmbr1, model VirtIO. Repeat for vmbr2.172.16.0.1/24192.168.0.1/24https://10.10.110.10 (the WAN IP) and see the pfSense login page (default admin / pfsense).pfsense until you do this.vtnet0/1/2 not by what you called them. Match by MAC address: in Proxmox VM 103 → Hardware, note each net's MAC, then in pfSense console option 1 it'll show the same MACs.A small Ubuntu Server VM in the DMZ that you SSH into first, then SSH from there into the LAN servers. It's the "secure entry point."
101, Name jumpbox, OS = Ubuntu Server ISO, OS type Linux 6.x.jumpbox, username tct_jumpbox (or whatever your team picks), strong password.sudo ufw default deny incoming sudo ufw default allow outgoing sudo ufw allow from 10.10.10.0/24 to any port 22 sudo ufw allow from 172.16.0.0/24 to any port 22 sudo ufw allow from 192.168.0.0/24 to any port 22 sudo ufw enable sudo ufw status verbose
ssh tct_jumpbox@172.16.0.X (whatever IP DHCP gave it) and reach a prompt.sudo systemctl status ssh; enable with sudo systemctl enable --now ssh.sudo ufw status to see the rules.ip -br addr.Install Windows Server 2025 (Datacenter Evaluation edition) on a VM that will eventually become a Domain Controller, DNS server, and IIS web server.
102, Name WinSrv, OS = Windows Server ISO, OS type Windows 11/2025.e1000 (Windows has built-in drivers for this; VirtIO needs extra drivers). Bridge vmbr2. Enable Proxmox firewall.Win+R → type ncpa.cpl → right-click Ethernet → Properties → double-click Internet Protocol Version 4 (TCP/IPv4) → set:
192.168.0.15255.255.255.0192.168.0.1192.168.0.1ipconfig /all → IPv4 Address shows 192.168.0.15(Preferred), DHCP Enabled: No.Another Ubuntu Server VM, this time on the LAN. It'll host web servers (NGINX or Apache) and the database (MariaDB).
105, Name LinuxServer, OS = Ubuntu Server ISO.ens18 → Edit IPv4 → Manual:
192.168.0.0/24192.168.0.20192.168.0.1192.168.0.1linuxserver, admin user, strong password.sudo systemctl enable ssh ip -br addr ping -c 2 192.168.0.1 sudo apt update && sudo apt upgrade -y
linuxserver, IP is 192.168.0.20/24, ping to 192.168.0.1 succeeds, apt update reaches Ubuntu's mirrors (proves NAT through pfSense works).A Ubuntu Desktop VM with Firefox — used to test web services from a "real user" perspective.
xubuntu-core^ on top:
sudo apt install -y xubuntu-core^ firefox lightdmsudo systemctl set-default graphical.target104, Name Linux-Ubuntu, OS = Ubuntu Desktop ISO (or Server ISO if going with the Xubuntu-core approach).192.168.0.25 via GNOME Settings → Network → Wired → ⚙ → IPv4 = Manual after install (or in subiquity if going with Server).http://192.168.0.20 — should reach LinuxServer (default NGINX or Apache page once installed in Phase 3).192.168.0.15192.168.0.20192.168.0.25Five VMs running, basic network alive. Now we add services.
Make pfSense automatically hand out IP addresses to anything that asks (DHCP) and let internal VMs look up names via pfSense's DNS resolver (Unbound).
https://10.10.110.10 or via SSH tunnel).172.16.0.100 – 172.16.0.200172.16.0.1192.168.0.100 – 192.168.0.200192.168.0.1cat /etc/resolv.conf shows 172.16.0.1 as nameserver. From the LinuxServer, nslookup google.com returns an IP. From any VM, curl https://www.google.com reaches Google.Apply a 9-rule firewall ruleset on pfSense's LAN interface that follows defense-in-depth: only allow what's needed, deny everything else.
| # | Action | Source | Destination | Description |
|---|---|---|---|---|
| 1 | PASS | LAN_NET | This Firewall · ICMP echoreq | Ping outbound (gateway only) |
| 2 | PASS | LAN_NET | This Firewall · UDP/123 | NTP to PFsense |
| 3 | PASS | LAN_NET | This Firewall · UDP/53 | DNS to PFsense |
| 4 | BLOCK | LAN_NET | This Firewall · any | Block servers → pfSense management |
| 5 | BLOCK | LAN_NET | DMZ_NET · any | Block servers → DMZ (no return-init) |
| 6 | BLOCK | LAN_NET | RFC1918 · any | Block other private-range access |
| 7 | PASS | LAN_NET | OUTBOUND_WEB · TCP/UDP | Outbound web/DNS/NTP only |
| 8 | PASS | LAN_NET | 5985–5986 · TCP | Windows Update/WinRM |
| 9 | PASS | LAN_NET | 445 · TCP | SMB |
Apply changes after adding all rules.
ping 1.1.1.1 fails (correct — only ICMP to gateway is allowed) but curl https://www.google.com succeeds (correct — port 443 in OUTBOUND_WEB). From WinSrv, Windows Update can fetch updates over HTTPS.Install a web stack so we have something for clients to hit. LAMP = Linux + Apache + MariaDB + PHP. LEMP = same but NGINX instead of Apache.
Detailed step-by-step (with click-to-copy commands) lives at LAMP + LEMP setup walkthrough →. That page covers both stacks side-by-side.
ssh user@192.168.0.20mysql_secure_installation → create capstone_dbPromote WinSrv to a Domain Controller. After this, WinSrv is the boss of a Windows network — managing user logins, computer accounts, security policies, DNS for the internal domain.
capstone.localcapstone.local · NextCAPSTONE auto-fills · Nextipconfig /all shows Primary DNS Suffix = capstone.local, DNS Servers = ::1 + 127.0.0.1. Server Manager left sidebar now has AD-DS and DNS entries.Test that everything works together from a real client's perspective.
| URL | Expected |
|---|---|
http://192.168.0.20/ | NGINX welcome page |
http://192.168.0.20:8080/ | Apache welcome page (if both stacks installed) |
http://192.168.0.20/dbtest.php | Database connection success message |
https://www.google.com/ | Loads normally (proves NAT through pfSense) |
nslookup linuxserver.capstone.local nslookup google.com ping 192.168.0.20 ping 172.16.0.100
ssh user@192.168.0.20 # SSH to LinuxServer curl http://192.168.0.20/ # web reachability ping -c 2 google.com # DNS + NAT ping -c 2 192.168.0.15 # WinSrv reachable
capstone.local createdYou've built a real, segmented, functioning network with services. That's it. That's the lab.
A flat list of every common issue, organized by symptom. Search this page (Cmd+F) when you hit a wall.
ip -br addr (Linux) or ipconfig /all (Windows).ping 192.168.0.1 (LAN VMs) or ping 172.16.0.1 (DMZ VMs).Cmd+Shift+R or Cmd+Option+R.| Symptom | Cause / Fix |
|---|---|
| No video output from server | Check the cable. ML350p Gen8 has VGA, not HDMI. Use a real VGA monitor or a VGA-to-HDMI converter. |
| Server beeps repeatedly on power | Memory error. Reseat the RAM modules — push them firmly until both latches snap. |
| "Press F1 to continue, F9 for setup" | BIOS event log filled. Press F1 to dismiss; clear in BIOS → System Options → Health Logs. |
| RAID drives not detected | Reseat drives. Check the cable from the Smart Array P420i to the backplane. POST should show the controller initializing. |
| Proxmox installer freezes | Bad RAID drive. Boot Intelligent Provisioning (F10) → diagnostics. Or install onto a single working drive temporarily. |
| Can't reach Proxmox web GUI from laptop | Wrong IP, wrong subnet, school firewall blocking 8006, or self-signed cert refused. SSH directly first to confirm Proxmox is alive. |
| Symptom | Cause / Fix |
|---|---|
| Lost SSH to Proxmox after editing /etc/network/interfaces | You edited vmbr0 by accident. Use the physical console; nano /etc/network/interfaces to fix; ifreload -a. |
| VM won't boot — black screen forever | Wrong machine type or bad ISO. For Linux Desktop: try Display = SPICE, Machine = q35. For Windows: BIOS = OVMF (UEFI). |
| Ubuntu Desktop installer hangs | Out of RAM (need 4 GB+) or graphics driver. Boot with "safe graphics" mode (press e at GRUB, append nomodeset). Or switch to Server + Xubuntu Core. |
| VM has no IP after install | NIC name changed (was ens18, became enp6s18 after machine type change). Set up the new interface name in Network settings. |
| pfSense WAN didn't get DHCP | School filters unfamiliar MACs. Reboot the VM, or have your instructor whitelist the MAC. |
| pfSense WAN got an IP but no internet | Default-deny "block private networks on WAN" rule is on. Disable it (Interfaces → WAN → uncheck "Block private networks") — your school IS a private network. |
| noVNC console won't paste | Known limitation. SSH from Proxmox host shell into the VM instead — that's paste-friendly. |
| Ping from VM to gateway works, but ping to internet fails | NAT on pfSense isn't configured (Firewall → NAT → Outbound = Automatic). Or the LAN firewall rule blocks ICMP outbound (correct behavior — try curl instead). |
| Static IP set but APIPA (169.254.x.x) shows up | (Windows) DHCP got disabled but static didn't apply. Use the GUI (ncpa.cpl), not netsh — more reliable. |
| Symptom | Cause / Fix |
|---|---|
| NGINX shows "Welcome to nginx" but won't render PHP | FastCGI socket path wrong. ls /run/php/, find the actual php*-fpm.sock, fix the path in /etc/nginx/sites-available/default, sudo systemctl reload nginx. |
| Apache fails with "AllowOverride not allowed here" | The Directory directive needs to be inside the VirtualHost. Move it from any conf-enabled file into /etc/apache2/sites-available/000-default.conf. |
| Two web servers conflict on port 80 | Move Apache to 8080: edit /etc/apache2/ports.conf change Listen 80 to Listen 8080, edit 000-default.conf change VirtualHost *:80 to *:8080. |
| MariaDB connection from PHP fails | Wrong password in PHP, or user doesn't have privileges. Run sudo mariadb → SHOW GRANTS FOR 'capuser'@'localhost'; |
| AD-DS install fails Prerequisites Check (red error) | Almost always: WinSrv has a DHCP IP (needs static), or hostname is still WIN-XXXXX default. Fix both, retry. |
| Domain join from another VM fails | Client's DNS must point at the DC's IP (so it can find the domain). Set DNS to WinSrv's IP, not pfSense. |
| Two DHCP servers fighting (Windows DHCP + pfSense DHCP) | Disable pfSense's LAN DHCP first (Services → DHCP Server → LAN tab → uncheck Enable). Then activate Windows DHCP role. |
| pfSense shows red "default password" warning | Change it (System → User Manager → admin → pencil → set strong password). |
| RFC1918 alias contains "10.0.0.o" (letter O) | Typo. Edit (Firewall → Aliases → IP → RFC1918) → fix to 10.0.0.0/8. |
| Browser can't reach internal web server but ping works | UFW blocking on the server. sudo ufw allow 80/tcp and sudo ufw allow 443/tcp. |
journalctl -xe or tail -f /var/log/syslog. Windows: Event Viewer → System log. The first red error is usually the cause; everything after is fallout.You've built the lab. Now what?
realm join capstone.local from Ubuntu, single-sign-on across the lab.