Back to Portfolio
docs Network · Topology
Network Topology & Infrastructure

Full physical and logical documentation of the homelab network — firewall, switching, server hypervisor and all running workloads. Every design decision is documented with reasoning.

FortiGate NGFW Cisco IOS Aruba AOS-CX Proxmox VE 20+ VMs
Stable Version v1.4 Updated 2026-04-12 8 min read
Physical Devices
FORTINET FortiGate · FortiOS
FortiGate Firewall
Fortinet · FortiOS
Edge Firewall
CISCO Catalyst · IOS
Cisco Catalyst Switch
Cisco IOS · Layer 2/3
Core Switch
ARUBA AOS-CX · L2/L3
HPE Aruba Switch
ArubaOS-CX · Layer 2
Access Switch
PROXMOX Virtual Environment KVM · QEMU · LXC
Proxmox VE Node
KVM/QEMU · 195 GB RAM
Hypervisor
Network Diagram
The diagram below is a conceptual reference showing the design pattern — VLAN segmentation, LACP uplinks, firewall placement and hypervisor fan-out. Subnet labels use the RFC 1918 private range 10.0.X.0/24 as illustrative addressing rather than the real production scheme. The structure is what matters; the numbers are placeholders.
CONCEPTUAL LAB TOPOLOGY INTERNET / WAN ISP Uplink WAN FortiGate Next-Gen Firewall FortiOS · IPS · App Control LACP 802.3ad 802.1Q Trunk Cisco Switch L2/L3 Core · RSTP IOS · SVIs · Port-Ch Aruba Switch L2 Access · BPDU Guard AOS-CX · DHCP/VLAN peer trunk Proxmox VE 195 GB RAM · 15 cores · KVM/QEMU PCIe GPU PT · Cloud-Init · ZFS Ubuntu VMs Service stack VLAN 10 · Servers RH RHEL VMs Enterprise VLAN 10 · SELinux Win Server AD · DNS · DHCP VLAN 20 · Management AI VM GPU passthrough Ollama · SD · CUDA Docker K8s · Portainer VLAN 10 · services VLANs: 10 Servers 20 Management 30 Users 40 IoT 999 Native (unused) 1 Default — DISABLED Cloudflare Tunnel → no open inbound WAN ports · WireGuard VPN → site-to-client remote access · Pi-hole → DNS filtering
Server Inventory
VM / HostOSVLANRoleKey Services
proxmox-01Proxmox VE 8.xVLAN 20HypervisorKVM host, Cloud-Init, ZFS storage
srv-ubuntu-01Ubuntu 22.04 LTSVLAN 10ServicesNextcloud, Vaultwarden, Gitea, n8n
srv-ubuntu-02Ubuntu 22.04 LTSVLAN 10MediaJellyfin, Plex, media storage
srv-ubuntu-03Ubuntu 22.04 LTSVLAN 10MonitoringGrafana, Prometheus, Uptime Kuma
srv-rhel-01RHEL 9VLAN 10EnterpriseProduction workloads, SELinux enforcing
win-server-01Windows Server 2022VLAN 20DirectoryActive Directory, DNS, DHCP, GPO
ai-workload-01Ubuntu 22.04 LTSVLAN 10AI / MLOllama, Stable Diffusion, ComfyUI, MLflow, Jupyter
docker-host-01Ubuntu 22.04 LTSVLAN 10ContainersDocker, Kubernetes, Portainer, Nginx Proxy Manager
cicd-01Ubuntu 22.04 LTSVLAN 10CI/CDGitea, Drone CI, automated pipelines
mailcow-01Ubuntu 22.04 LTSVLAN 10MailMailcow — SMTP, IMAP, DKIM, SPF, DMARC
docs Network · Protocols
Network Protocols

Detailed configuration and explanation of every protocol in use — VLANs, LACP, STP, routing, DHCP and DNS. Each section includes working CLI config, reasoning and links to official standards.

Stable Version v1.4 Updated 2026-04-18 24 min read
802.1Q — Virtual LANs
IEEE 802.1Q adds a 4-byte tag to Ethernet frames containing a 12-bit VLAN ID (supporting 4096 VLANs). This allows a single physical link to carry traffic for multiple isolated networks simultaneously. The key security rule: never use VLAN 1 for any production traffic — it is the default on all ports of all vendors and is a well-known attack vector.
VLANNameSubnetGatewayDHCP RangeInternet
10Servers10.0.10.0/2410.0.10.1.10 – .200Allowed
20Management10.0.20.0/2410.0.20.1.10 – .50Restricted
30Users10.0.30.0/2410.0.30.1.10 – .200Allowed
40IoT10.0.40.0/2410.0.40.1.10 – .150Allowed
999Unused-NativeBlocked
1defaultDisabled
Cisco IOS — VLAN + trunk configuration IOS
! ── Create VLANs ──────────────────────────────────────────
vlan 10
 name Servers
vlan 20
 name Management
vlan 30
 name Users
vlan 40
 name IoT
vlan 999
 name Unused-Native
!
! ── Disable VLAN 1 — security hardening ──────────────────
interface Vlan1
 no ip address
 shutdown
!
! ── Trunk to FortiGate — allow only named VLANs ──────────
interface GigabitEthernet0/1
 description Uplink_to_FortiGate_LAG
 switchport mode trunk
 switchport trunk allowed vlan 10,20,30,40
 switchport trunk native vlan 999
 no shutdown
!
! ── Access port — server on VLAN 10 ─────────────────────
interface GigabitEthernet0/10
 description Proxmox_Node_1
 switchport mode access
 switchport access vlan 10
 spanning-tree portfast
 spanning-tree bpduguard enable
 no shutdown
!
! ── L3 SVI — inter-VLAN routing on Cisco ────────────────
interface Vlan10
 ip address 10.0.10.2 255.255.255.0
 no shutdown
!
! ── Verify ───────────────────────────────────────────────
show vlan brief
show interfaces trunk
IEEE 802.3ad — LACP Link Aggregation
LACP (802.3ad / 802.1AX) bundles multiple physical interfaces into one logical channel. Result: combined bandwidth + automatic failover. The protocol exchanges PDUs (Protocol Data Units) between the two ends to negotiate which ports are active. If one physical link fails, traffic shifts to remaining links within milliseconds — no topology reconvergence needed.
Cisco IOS — Port-Channel (LACP active mode) IOS
! ── Create the logical Port-Channel interface first ──────
interface Port-channel1
 description LAG_to_FortiGate
 switchport mode trunk
 switchport trunk allowed vlan 10,20,30,40
 switchport trunk native vlan 999
!
! ── Assign physical members — active mode initiates LACP ─
interface GigabitEthernet0/1
 description LAG_link1_to_FortiGate
 channel-group 1 mode active
 no shutdown

interface GigabitEthernet0/2
 description LAG_link2_to_FortiGate
 channel-group 1 mode active
 no shutdown
!
! ── Verify ───────────────────────────────────────────────
show etherchannel summary
show lacp neighbor
show interfaces port-channel 1 status
LACP ModeBehaviourPDUs sent?Used where
activeInitiates negotiationYesCisco switch — proactively forms LAG
passiveResponds onlyOnly if peer sends firstFortiGate — responds to Cisco
onStatic — no negotiationNoNot used — no failover detection
IEEE 802.1w — Rapid Spanning Tree (RSTP)
Without STP, any physical loop in the switch fabric causes a broadcast storm — frames loop endlessly, saturating all links and crashing the entire network in seconds. RSTP elects a root bridge and blocks redundant paths, keeping exactly one active path. RSTP converges in under 1 second vs 30–50 seconds for classic STP.
Cisco IOS — RSTP + edge port hardening IOS
! ── Enable RSTP globally ──────────────────────────────────
spanning-tree mode rapid-pvst

! ── Set Cisco as root for all VLANs (priority 4096) ──────
! Default priority is 32768 — lower wins the election
spanning-tree vlan 10,20,30,40 priority 4096

! ── Edge ports — skip 30s listening/learning ────────────
! PortFast: use ONLY on ports connected to end devices
interface range GigabitEthernet0/10 - 24
 spanning-tree portfast
 ! BPDU Guard: shut port immediately if BPDU received
 ! Prevents rogue switch from joining topology
 spanning-tree bpduguard enable

! ── Enable BPDU Guard globally on all PortFast ports ─────
spanning-tree portfast bpduguard default

! ── Auto-recover from err-disable after 5 minutes ───────
errdisable recovery cause bpduguard
errdisable recovery interval 300

! ── Verify ───────────────────────────────────────────────
show spanning-tree vlan 10
show spanning-tree summary
FeatureSettingPort typeEffect
RSTP moderapid-pvstAllSub-second convergence on topology change
Root priority4096Switch globalCisco core always wins root election
PortFastEnabledAccess ports onlyDevices reach network immediately, no 30s wait
BPDU GuardEnabledAll PortFast portsPort shuts if another switch is plugged in
Loop GuardEnabledNon-designated uplinksPrevents unidirectional failures creating loops
Network Rack — Physical Layout, Protocols & SFP Uplinks

Every unit in the rack plays a specific role in the protocol stack. Hover over any device to see what it does — in both plain and technical language.

HOMELAB RACK U1 U2 U3 U5 U7 U9 U11 U13 U15 U17 U19 U21 ISP Modem / ONT WAN uplink · Dynamic IP · SFP upstream SFP Patch Panel — 24 port Cat6A · cable management FortiGate FGT-A — PRIMARY NGFW · NAT · IKEv2 · OSPF · BGP · DDNS · HA Active WAN1(RJ45) · WAN2(SFP+) · LAN(LAG) · HA-sync W1 SFP+ HA HA heartbeat · sync FortiGate FGT-B — SECONDARY (Passive) HA Passive · Config mirror · Failover <1s · Standby Silent — takes over automatically if FGT-A fails SFP+ — spacer — Cisco Catalyst — CORE L3 STP Root · LACP · OSPF · VLANs · SVIs · ACLs Gi0/1-2: LACP→FGT Gi0/3: trunk→Aruba Gi0/10+: access SFP+ SFP+ LACP Port-Channel 1 Aruba AOS-CX — ACCESS L2 VLAN access · 802.1Q trunk · BPDU Guard · DHCP relay SFP Server Patch Panel — 24 port · server-side cabling Proxmox VE — HYPERVISOR 195 GB RAM · 15 cores · 20+ VMs · ZFS · GPU-PT KVM/QEMU · Cloud-Init · br-vlan10/20/30/40 bridges NVMe ZFS GPU Pi-hole — DNS Filter + Ad Blocker Network-wide DNS · FTLDNS · DoT upstream · blocklist Windows Server 2022 — AD DS Active Directory · DNS · DHCP · Group Policy · Kerberos AD DS DNS DHCP UPS — Uninterruptible Power Supply Battery backup · SNMP · NUT · graceful shutdown ~15 min runtime at full load WAN / ISP DHCP from ISP Dynamic IP HA Heartbeat config sync LACP 802.3ad Port-Channel1 SFP+ × 2 OSPF 802.1Q Trunk VLAN 10 trunk access BGP ISP peering PROTOCOL LEGEND WAN / ISP / DHCP HA Heartbeat LACP / SFP+ bond 802.1Q Trunk VLAN access OSPF OSPF area label BGP BGP peering label Hover any unit for details
OSPF — Open Shortest Path First (RFC 2328)
OSPF is a link-state interior routing protocol — instead of static routes, routers learn the entire network topology and calculate the shortest path to every destination dynamically. If a link fails, OSPF reconverges automatically in seconds. In this lab, OSPF runs in Area 0 (backbone) between the FortiGate and Cisco L3 switch — meaning both devices always know all routes without manual configuration.
ConceptValueExplanation
Protocol typeLink-state (LSA flooding)Each router shares its link state with all others — every router has the same map of the network
AlgorithmDijkstra SPFShortest Path First — calculates least-cost path to every destination
AreaArea 0 — backboneSingle area lab — all routers in same area, full LSA exchange
Adjacency typePoint-to-pointFortiGate ↔ Cisco SVI — no DR/BDR election needed
Hello interval10s (default)Routers exchange Hello packets to discover and maintain neighbours
Dead interval40sIf no Hello received in 40s, neighbour declared dead and routes removed
MetricCost (based on bandwidth)Higher bandwidth = lower cost = preferred path
RedistributionStatic → OSPFDefault route redistributed from FortiGate into OSPF so Cisco knows where to send internet traffic
FortiGate — OSPF configuration FortiOS CLI
# ── Enable OSPF on FortiGate ──────────────────────────────
config router ospf
  set router-id      10.0.20.1   # FortiGate loopback / mgmt IP
  set redistribute   connected     # share all connected networks
  set redistribute   static        # share default route 0.0.0.0/0 to internet
  config area
    edit 0.0.0.0                    # Area 0 — backbone
    next
  end
  config ospf-interface
    edit "ospf-to-cisco"
      set interface    "vlan20"     # mgmt VLAN — connects to Cisco SVI
      set area         0.0.0.0
      set network-type point-to-point
      set hello-interval  10
      set dead-interval   40
    next
  end
  config network
    edit 1
      set prefix   10.0.0.0 255.255.0.0  # advertise all VLANs
      set area     0.0.0.0
    next
  end
end

# ── Redistribute default route to Cisco ──────────────────
config router ospf
  set default-information-originate always
end
Cisco IOS — OSPF Area 0 IOS
! ── Enable OSPF process on Cisco L3 switch ───────────────
router ospf 1
 router-id     10.0.20.2        ! Cisco SVI management IP
 passive-interface default        ! never send OSPF on access ports
 no passive-interface Vlan20       ! OSPF only on mgmt SVI toward FGT
 network 10.0.0.0 0.0.255.255 area 0  ! all SVIs in area 0
 default-information originate    ! accept default route from FortiGate
!
! ── OSPF on the uplink SVI to FortiGate ─────────────────
interface Vlan20
 ip ospf 1 area 0
 ip ospf network point-to-point
 ip ospf hello-interval 10
 ip ospf dead-interval  40
!
! ── Verify ───────────────────────────────────────────────
show ip ospf neighbor
show ip ospf database
show ip route ospf
BGP — Border Gateway Protocol (RFC 4271)
BGP is the routing protocol of the entire internet — every ISP, CDN and cloud provider uses it. In a homelab/SMB context, BGP is configured on the FortiGate when you have dual WAN (two ISPs) or a dedicated IP block. The FortiGate peers with the ISP router using BGP, receives full or partial routing tables, and can advertise your own IP prefix. Even with a single ISP, understanding BGP matters for enterprise networking roles.
ConceptValueExplanation
Protocol typePath-vector, exteriorCarries path of AS numbers — prevents loops, enables policy-based routing
Session typeeBGP (external)Between FortiGate (homelab AS) and ISP router — different Autonomous Systems
TransportTCP port 179BGP runs over TCP — reliable, ordered delivery
Homelab AS65001 (private range)Private AS 64512–65535 for internal use; ISP has a real public AS number
Route selectionBest path algorithmPrefers: highest LOCAL_PREF → lowest AS_PATH → lowest MED → eBGP over iBGP → lowest router-id
Dual WAN usePrimary/backup ISPFortiGate sets LOCAL_PREF to prefer WAN1; if WAN1 fails, traffic shifts to WAN2 automatically
Keepalive60s (Hold timer: 180s)BGP sends keepalives to maintain session; if no message in 180s, session reset
FortiGate — eBGP with ISP (dual WAN failover) FortiOS CLI
# ── BGP configuration — FortiGate peering with ISP ───────
config router bgp
  set as          65001            # our private AS number
  set router-id   10.0.20.1      # FortiGate management IP

  config neighbor
        # ── ISP 1 peer (WAN1) ──────────────────────────────
    edit 203.0.113.1               # ISP 1 gateway IP
      set remote-as     64500      # ISP 1 public AS
      set interface     "wan1"
      set update-source "wan1"
            # LOCAL_PREF 200 = strongly prefer WAN1
      set route-map-in  "SET_LOCALPREF_WAN1"
      set keepalive-timer 60
      set holdtime-timer  180
    next
        # ── ISP 2 peer (WAN2 — backup) ─────────────────────
    edit 198.51.100.1              # ISP 2 gateway IP
      set remote-as     64501      # ISP 2 public AS
      set interface     "wan2"
      set update-source "wan2"
            # LOCAL_PREF 100 = use WAN2 only as backup
      set route-map-in  "SET_LOCALPREF_WAN2"
    next
  end

    # ── Route maps to set LOCAL_PREF per ISP ──────────────
  config route-map
    edit "SET_LOCALPREF_WAN1"
      config rule
        edit 1
          set set-local-preference 200
        next
      end
    next
    edit "SET_LOCALPREF_WAN2"
      config rule
        edit 1
          set set-local-preference 100
        next
      end
    next
  end
end

# ── Verify ────────────────────────────────────────────────
get router info bgp summary
get router info bgp neighbors
get router info routing-table bgp
DHCP — Dynamic Host Configuration Protocol (RFC 2131)
Each VLAN needs its own DHCP scope — otherwise devices in different VLANs would get IPs from the wrong range and routing would break. The FortiGate acts as DHCP server per VLAN interface, and the Aruba switch uses an IP helper-address (DHCP relay) to forward DHCP broadcasts from access ports up to the FortiGate server across VLAN boundaries.
VLANDHCP ServerScopeGatewayDNS
VLAN 10FortiGate10.0.10.10–.20010.0.10.110.0.10.53 (Pi-hole)
VLAN 20Windows Server AD10.0.20.10–.5010.0.20.110.0.20.10 (AD DNS)
VLAN 30FortiGate10.0.30.10–.20010.0.30.110.0.10.53 (Pi-hole)
VLAN 40FortiGate10.0.40.10–.15010.0.40.110.0.10.53 (Pi-hole)
Aruba AOS-CX — DHCP relay (IP helper) per VLAN AOS-CX
# ── DHCP relay on Aruba access switch ─────────────────────
# When a device on VLAN 30 sends a DHCP broadcast,
# Aruba forwards it as unicast to FortiGate 10.0.30.1
interface vlan30
  ip address 10.0.30.2/24
  ip helper-address 10.0.30.1   # FortiGate as DHCP server
  no shutdown

interface vlan40
  ip address 10.0.40.2/24
  ip helper-address 10.0.40.1
  no shutdown
FortiGate HA — Active/Passive Cluster (FGCP)
FortiGate HA uses FGCP (FortiGate Clustering Protocol) — not VRRP or HSRP. FGCP is proprietary to Fortinet. The active unit owns all interfaces including the virtual cluster IP. The passive unit receives a full configuration mirror and session table sync via the heartbeat link. Failover is sub-second and stateful — existing TCP sessions survive the failover without dropping.
FeatureDetailExplanation
ProtocolFGCPFortiGate Clustering Protocol — proprietary L2 protocol on heartbeat interface
ModeActive-PassiveFGT-A handles all traffic; FGT-B is on hot standby with full config sync
HeartbeatDedicated HA portDirect crossover link between FGT-A and FGT-B — not through any switch
Session syncYes — statefulActive sessions (TCP, VPN, NAT translations) synced to passive; no drops on failover
Failover triggerLink failure, CPU overload, process crashHA monitors interface status, CPU load and daemon health
Failover time<1 secondPassive takes over virtual MAC and IP; downstream switches see no change
Config syncAutomatic, continuousAny config change on active is instantly pushed to passive unit
Virtual MACShared cluster MACBoth units share a virtual MAC per interface so ARP cache on switches never changes
FortiGate — HA cluster configuration (FGT-A primary) FortiOS CLI
# ── Configure HA on FGT-A (primary) ──────────────────────
config system ha
  set mode          a-p              # active-passive
  set group-name    "homelab-cluster"
  set group-id      1
  set password      "HA-secret-key"  # must match on both units
  set priority      200             # higher = primary (FGT-A=200, FGT-B=100)
  set hbdev         "port4"  50    # heartbeat on port4, priority 50
  set session-sync-dev "port4"      # session table sync on same link
  set override      enable           # re-elect primary after failure recovery
  set monitor       "wan1" "port1"  # monitor these interfaces for failover trigger
end

# ── Same config on FGT-B but priority 100 ────────────────
# set priority 100

# ── Verify cluster status ─────────────────────────────────
get system ha status
diagnose sys ha status
diagnose sys ha checksum show   # config sync verification
DHCP Snooping & Dynamic ARP Inspection (DAI)
DHCP snooping prevents rogue DHCP servers — if a device plugged into an access port tries to hand out IPs, the switch drops its DHCP responses. Only the trusted uplink port (toward FortiGate/Aruba) is marked trusted. DAI then uses the DHCP snooping binding table to validate ARP packets — blocking ARP spoofing and man-in-the-middle attacks at layer 2.
Cisco IOS — DHCP Snooping + DAI IOS
! ── DHCP Snooping — per VLAN ─────────────────────────────
ip dhcp snooping
ip dhcp snooping vlan 10,30,40
no ip dhcp snooping information option   ! remove option 82 (causes issues)
!
! ── Trust only the uplink port to FortiGate ──────────────
interface Port-channel1
 ip dhcp snooping trust
!
! Access ports are untrusted by default — rogue DHCP blocked
!
! ── Dynamic ARP Inspection — per VLAN ───────────────────
ip arp inspection vlan 10,30,40
ip arp inspection validate src-mac dst-mac ip
!
! ── Trust the uplink for ARP too ─────────────────────────
interface Port-channel1
 ip arp inspection trust
!
! ── Verify ───────────────────────────────────────────────
show ip dhcp snooping binding
show ip arp inspection vlan 10
docs Security · Firewall
Security & Firewall

All security policies, ACLs, NAT rules and port security configurations. Every rule has documented reasoning — not just what was configured, but why.

FortiGate NGFWACLNAT / VIPPort SecurityZero Trust
Stable Version v1.4 Updated 2026-04-09 6 min read
FortiGate — Firewall Policy Table
Object-based model: FortiGate does not use raw IP addresses in rules. Every source, destination and service is a named object. When a server IP changes, you update the object once and all policies referencing it automatically update — no manual rule editing.
IDPolicy NameSrc InterfaceDst InterfaceActionNATLog
10Servers-to-Internetvlan10wan1ACCEPTPATall
20Users-to-Internetvlan30wan1ACCEPTPATutm
30IoT-Internet-onlyvlan40wan1ACCEPTPATutm
35IoT-block-Serversvlan40vlan10DENYall
36IoT-block-Mgmtvlan40vlan20DENYall
40Users-to-Serversvlan30vlan10ACCEPTutm
50Inbound-Cloudflarewan1vlan10ACCEPTVIPall
99Default-Deny-AllanyanyDENYall
FortiGate — address objects + IoT isolation policy FortiOS
# ── Address objects — reused across all policies ─────────
config firewall address
  edit "VLAN10-Servers"
    set subnet 10.0.10.0 255.255.255.0
  next
  edit "VLAN20-Management"
    set subnet 10.0.20.0 255.255.255.0
  next
  edit "VLAN40-IoT"
    set subnet 10.0.40.0 255.255.255.0
  next
end

# ── VIP — inbound DNAT for Cloudflare Tunnel ─────────────
config firewall vip
  edit "VIP-Cloudflare"
    set extintf "wan1"
    set extip   0.0.0.0
    set mappedip "10.0.10.20-10.0.10.20"
    set portforward enable
    set protocol tcp
    set extport    443
    set mappedport 443
  next
end

# ── Deny IoT from reaching servers — logged ───────────────
config firewall policy
  edit 35
    set name      "IoT-block-Servers"
    set srcintf   "vlan40"
    set dstintf   "vlan10"
    set srcaddr   "VLAN40-IoT"
    set dstaddr   "VLAN10-Servers"
    set action    deny
    set schedule  "always"
    set service   "ALL"
    set logtraffic all
  next
end
NAT — Outbound PAT and Inbound VIP
Outbound (PAT / SNAT): all internal VLANs share the single WAN IP. FortiGate tracks connections by source port number. Inbound (VIP / DNAT): no ports are open on the WAN. All public-facing services are exposed via Cloudflare Tunnel — an encrypted outbound connection that Cloudflare proxies. This means zero open inbound firewall rules.
TypeDirectionMechanismApplied to
PAT / OverloadOutboundMany:1 — all hosts share WAN IP, differentiated by portAll VLANs to internet
VIP (DNAT)InboundWAN IP:port mapped to internal host:portCloudflare Tunnel connector only
Cloudflare TunnelOutbound (public)Outbound persistent tunnel — Cloudflare proxies inbound trafficAll public-facing services
Port Security — Cisco IOS
Cisco IOS — full access port security profile IOS
! ── Template — apply to all access ports ────────────────
interface GigabitEthernet0/10
 description Server_Access_VLAN10
 switchport mode access
 switchport access vlan 10
 ! Port security — max 3 MACs, restrict + log violations
 switchport port-security maximum 3
 switchport port-security violation restrict
 switchport port-security
 ! PortFast — immediate forwarding (no STP delay)
 spanning-tree portfast
 ! BPDU Guard — shut if another switch connects
 spanning-tree bpduguard enable
 ! Storm control — max 10% broadcast, 10% multicast
 storm-control broadcast level 10.00
 storm-control multicast level 10.00
 storm-control action shutdown
 no shutdown
!
! Violation modes:
!  protect  = drop + no log, port stays up
!  restrict = drop + SNMP trap log, port stays up   ← used
!  shutdown = err-disable immediately (strictest)
docs Infrastructure · Services
Self-Hosted Services

Every service running on the homelab infrastructure. For each: what it does, where it runs, official documentation and configuration notes.

Stable Version v1.4 Updated 2026-04-22 9 min read
Infrastructure
Proxmox VE
Hypervisor

Open-source KVM/QEMU hypervisor with a web UI. Manages all VMs, networking bridges and ZFS storage. Cloud-Init integration enables zero-touch VM provisioning.

VLAN 20 · 195 GB RAM · 15 cores · ZFS storage
Docker + Kubernetes
Container Orchestration

Docker runs containerised services. Kubernetes handles orchestration for multi-container workloads. Portainer provides a visual management UI for both.

VLAN 10 · docker-host-01 VM · Compose v2
Nginx Proxy Manager
Reverse Proxy

Manages all reverse proxy rules and HTTPS termination. Routes external hostnames to the correct internal service. Integrated Let's Encrypt for automatic certificate renewal.

Port 80/443/81 · Let's Encrypt · Docker
Productivity & Data
Nextcloud
Cloud Storage

Self-hosted alternative to Google Drive / OneDrive. File sync, sharing, calendar, contacts and collaborative document editing. Data never leaves the homelab.

Port 8080 · MariaDB · VLAN 10
Vaultwarden
Password Manager

Lightweight Bitwarden-compatible server. Full Bitwarden clients (browser extensions, mobile apps) connect to this private instance. TOTP, passkeys and secure sharing supported.

Port 8181 · SQLite · Docker
Gitea
Git Server

Self-hosted GitHub alternative. Repositories, issues, pull requests, webhooks and CI/CD trigger integration with Drone CI. Full Git over SSH and HTTPS.

Port 3000 · SQLite · VLAN 10
Mailcow
Email Server

Full email stack — Postfix (SMTP), Dovecot (IMAP), Rspamd (spam filtering), ClamAV (antivirus), SOGo webmail. DKIM, SPF and DMARC configured for mail delivery.

Ports 25/143/587/993 · Docker Compose
Monitoring & Automation
Grafana
Dashboards

Visualises metrics from Prometheus. Custom dashboards for CPU, RAM, network I/O, disk usage and service health across all VMs. Alerting via email and webhook.

Port 3030 · Prometheus datasource
Prometheus
Metrics Collection

Scrapes metrics from Node Exporter on every VM, cAdvisor on container hosts and application exporters. Time-series database. PromQL for custom queries.

Port 9090 · Node Exporter on all VMs
n8n
Workflow Automation

Visual no-code/low-code workflow automation. Connects services, triggers on events, runs scheduled tasks. Integrates with Gitea webhooks, Grafana alerts and external APIs.

Port 5678 · Docker · VLAN 10
Drone CI
CI/CD Pipelines

Triggered by Gitea webhooks on every push. Runs pipelines: build, test, lint, deploy. Docker-native — each pipeline step runs in an isolated container.

Port 3001 · Gitea OAuth · Docker runner
AI / ML Platform
Ollama
Local LLM Inference

Runs large language models locally — Llama 3, Mistral, Phi, CodeLlama and others. OpenAI-compatible API endpoint. Models run on GPU via PCIe passthrough. Zero data to cloud.

GPU VM · CUDA · Port 11434
Stable Diffusion + ComfyUI
AI Image Generation

Local Stable Diffusion inference with ComfyUI as the node-based workflow editor. Runs SDXL, SD 1.5 and LoRA fine-tuned models. GPU-accelerated via CUDA passthrough.

GPU VM · ComfyUI Port 8188 · CUDA
MLflow
ML Experiment Tracking

Tracks ML experiments — parameters, metrics, artifacts and model versions. Integrates with PyTorch and TensorFlow training loops. Provides a UI to compare runs and promote models.

Port 5000 · Docker · PostgreSQL backend
JupyterLab
Research Environment

Interactive notebooks for Python data science, ML experimentation and documentation. Runs on the AI VM with direct access to PyTorch/TensorFlow and GPU compute.

Port 8888 · GPU VM · Python 3.11
Media
Jellyfin
Media Server

Open-source media server. Streams video, music and photos to any device. Hardware transcoding via GPU for efficient streaming without high CPU load.

Port 8096 · VLAN 10 · GPU transcoding
Plex
Media Server

Alternative media server to Jellyfin with broader client support. Runs alongside Jellyfin on the same VM, sharing the same media library mount.

Port 32400 · VLAN 10
docs Security · VPN & DDNS
VPN & DDNS

Complete documentation of the FortiGate DDNS configuration and IPsec site-to-site VPN setup. Because the WAN IP is dynamic (ISP does not provide a static IP), a DDNS hostname is configured on the FortiGate so remote peers can always reach the firewall by name, not by IP.

FortiGate DDNS IPsec IKEv2 Site-to-Site WireGuard Dynamic IP
Stable Version v1.4 Updated 2026-04-15 11 min read
Why DDNS is Needed
Most residential and SMB internet connections use a dynamic WAN IP — the ISP changes it periodically or on every reconnection. A site-to-site VPN requires the remote peer to know where to connect. Without a fixed IP, the tunnel breaks every time the IP changes. DDNS (Dynamic DNS) solves this: the FortiGate automatically updates a DNS hostname whenever its WAN IP changes. The remote peer connects to the hostname, not the IP.
ProblemSolutionHow it works
WAN IP changes dynamicallyDDNS hostnameFortiGate polls or pushes updated IP to a DDNS provider whenever WAN IP changes
VPN peer needs stable addressHostname in VPN configRemote peer uses homelab.fortiddns.com — resolves to current WAN IP automatically
Self-signed cert mismatchFortiGate built-in DDNSFortiGate DDNS integrates with FortiGuard — no external account needed, managed in CLI/GUI
FortiGate DDNS — Configuration
FortiGate has a built-in DDNS service called FortiDDNS — it registers a *.fortiddns.com subdomain tied to your FortiGate's serial number. No external DDNS provider account is needed. The FortiGate updates the record automatically whenever the WAN IP changes. You can also configure third-party DDNS providers (No-IP, Dynu, DynDNS) via the same interface.
FortiGate — DDNS configuration (FortiGuard / FortiDDNS) FortiOS CLI
# ── Enable FortiGate built-in DDNS (FortiDDNS) ───────────
# This registers a hostname: <serial>.fortiddns.com
config system ddns
  edit 1
    set ddns-server    FortiGuardDDNS
    set use-public-ip  enable
    set update-interval 300         # update every 5 minutes
    set monitor-interface "wan1"   # watch WAN interface for IP change
    set ddns-domain    "homelab.fortiddns.com"
  next
end

# ── Alternative: third-party DDNS provider (e.g. No-IP) ──
config system ddns
  edit 2
    set ddns-server    No-IP
    set ddns-username  "your-noip-username"
    set ddns-password  "your-noip-password"
    set ddns-domain    "homelab.ddns.net"
    set monitor-interface "wan1"
    set update-interval 300
  next
end

# ── Verify DDNS is updating correctly ─────────────────────
diagnose debug application ddnsd -1
diagnose debug enable
# Then check: current IP in DDNS record should match WAN IP
get system ddns
Site-to-Site IPsec VPN — How It Works
A site-to-site VPN creates an encrypted tunnel between two physical locations — treating them as one logical network. Traffic between site A and site B travels through the tunnel, encrypted, over the internet. IKEv2 is used for tunnel negotiation; IPsec ESP encrypts the actual data. Because one side has a dynamic IP, the DDNS hostname is used as the peer identifier.
Connection Flow
StepProtocolWhat happens
1 — DNS ResolutionDNSRemote peer resolves homelab.fortiddns.com → current WAN IP of homelab FortiGate
2 — IKE Phase 1IKEv2 UDP 500/4500Both sides authenticate using pre-shared key (PSK) and negotiate encryption parameters (AES-256, SHA-256)
3 — IKE Phase 2IKEv2Child SA negotiated — defines which traffic is protected (interesting traffic selectors)
4 — Tunnel UpIPsec ESPEncrypted tunnel established. Traffic between defined subnets routes through tunnel automatically
5 — IP ChangeDDNS + IKE DPDIf WAN IP changes: DDNS updates, remote peer re-resolves hostname, IKE Dead Peer Detection triggers re-negotiation
FortiGate — IPsec Site-to-Site VPN Configuration
FortiGate — IPsec VPN Phase 1 (IKEv2, DDNS peer) FortiOS CLI
# ── Phase 1 — IKE negotiation ─────────────────────────────
# This defines HOW the tunnel is negotiated
config vpn ipsec phase1-interface
  edit "SiteB-VPN"
    set interface     "wan1"
    set ike-version   2
        # Remote peer — use DDNS hostname, not static IP
    set remote-gw-ddns "siteB.fortiddns.com"
        # Authentication with pre-shared key
    set authmethod    psk
    set psksecret     "YourStrongPreSharedKey!2024"
        # Encryption — AES-256-GCM is preferred for IKEv2
    set proposal      aes256gcm-prfsha384
    set dhgrp         20   # ECDH P-384
        # Dead Peer Detection — detect if tunnel drops
    set dpd           on-demand
    set dpd-retrycount 3
    set dpd-retryinterval 10
        # Local gateway ID = DDNS hostname of THIS FortiGate
    set localid       "homelab.fortiddns.com"
    set localid-type  fqdn
    set mode-cfg      disable
    set net-device    enable
  next
end
FortiGate — IPsec VPN Phase 2 (interesting traffic selectors) FortiOS CLI
# ── Phase 2 — what traffic goes through the tunnel ────────
config vpn ipsec phase2-interface
  edit "SiteB-VPN-P2"
    set phase1name   "SiteB-VPN"
        # Traffic selectors — subnets on each side
    set src-subnet   10.0.10.0 255.255.255.0   # local LAN (homelab)
    set dst-subnet   192.168.1.0 255.255.255.0  # remote LAN (site B)
        # ESP encryption for data
    set proposal     aes256gcm
    set dhgrp        20
        # Rekey every 8 hours
    set keylifeseconds 28800
    set auto-negotiate enable
  next
end

# ── Static route — send remote subnet through tunnel ──────
config router static
  edit 10
    set dst       192.168.1.0 255.255.255.0
    set device    "SiteB-VPN"
    set comment   "Route to Site B through IPsec tunnel"
  next
end

# ── Firewall policy — allow traffic through tunnel ─────────
config firewall policy
  edit 60
    set name      "LAN-to-SiteB"
    set srcintf   "vlan10"
    set dstintf   "SiteB-VPN"
    set srcaddr   "VLAN10-Servers"
    set dstaddr   "SiteB-LAN"
    set action    accept
    set schedule  "always"
    set service   "ALL"
    set logtraffic all
  next
  edit 61
    set name      "SiteB-to-LAN"
    set srcintf   "SiteB-VPN"
    set dstintf   "vlan10"
    set srcaddr   "SiteB-LAN"
    set dstaddr   "VLAN10-Servers"
    set action    accept
    set schedule  "always"
    set service   "ALL"
    set logtraffic all
  next
end
FortiGate — Verification and troubleshooting FortiOS CLI
# ── Check tunnel status ───────────────────────────────────
get vpn ipsec tunnel summary
get vpn ipsec tunnel details

# ── Check IKE SA (Phase 1) ────────────────────────────────
diagnose vpn ike status
diagnose vpn ike gateway list

# ── Check IPsec SA (Phase 2) ─────────────────────────────
diagnose vpn tunnel list

# ── Real-time debug (Phase 1 negotiation) ────────────────
diagnose debug application ike -1
diagnose debug enable
# Run the above, then bring tunnel up, check output
diagnose debug disable

# ── Ping through tunnel to verify routing ────────────────
execute ping-options source 10.0.10.1   # source from inside VLAN
execute ping 192.168.1.1                   # ping remote site B gateway

# ── Force tunnel re-negotiation ──────────────────────────
diagnose vpn ike restart
WireGuard — Remote Access VPN
While IPsec handles site-to-site between two fixed locations, WireGuard is used for remote client access — connecting a laptop or phone to the homelab from anywhere. WireGuard is a modern, lean protocol (4000 lines of code vs IPsec's ~400,000). It uses public key cryptography — no passwords, no certificates authority required. The FortiGate terminates the WireGuard tunnel on the WAN interface, and the client gets a private IP on VLAN 20 (management access).
FortiGate — WireGuard interface configuration FortiOS CLI
# ── Create WireGuard interface ────────────────────────────
config system wireguard
  edit "wg0"
    set listen-port   51820
    set private-key   "<FortiGate-private-key-base64>"
        # Assign IP from a dedicated WireGuard subnet
    config peers
      edit 1
        set name        "laptop-client"
        set public-key  "<client-public-key-base64>"
        set allowed-ips 10.0.50.2 255.255.255.255
      next
    end
  next
end

# ── Client config (wg0.conf on laptop) ───────────────────
# [Interface]
# PrivateKey = <client-private-key>
# Address    = 10.0.50.2/24
# DNS        = 10.0.40.1  # Pi-hole
#
# [Peer]
# PublicKey  = <FortiGate-public-key>
# Endpoint   = homelab.fortiddns.com:51820  # DDNS hostname
# AllowedIPs = 10.0.0.0/16  # route all homelab traffic
# PersistentKeepalive = 25
DDNS + VPN Flow Diagram
Homelab Dynamic WAN IP FortiGate homelab.fortiddns.com 10.0.10.0/24 IP update every 5min DDNS Server FortiGuard / No-IP homelab → current IP Internet UDP 500/4500 Remote Site / Client FortiGate / Client connects to DDNS hostname 192.168.1.0/24 IPsec ESP — AES-256-GCM encrypted tunnel ① WAN IP changes ② DNS updated ③ Peer resolves hostname ④ IKEv2 handshake
IPsec Parameters Reference
ParameterValue usedWhy
IKE VersionIKEv2More efficient than IKEv1, better handling of NAT traversal and DDNS peers
Phase 1 EncryptionAES-256-GCMAEAD cipher — encryption and authentication in one pass, no separate HMAC needed
DH GroupGroup 20 (P-384)Elliptic curve key exchange — strong security with shorter keys than classic DH
AuthenticationPSK (Pre-Shared Key)Simpler than PKI certificates for a homelab; use a strong random 32+ char key
Local ID typeFQDNIdentifies this FortiGate by its DDNS hostname, not IP — needed for dynamic WAN
DPD (Dead Peer Detection)on-demandSends keepalive only when there is traffic — detects dropped tunnels without flooding
Phase 2 Lifetime28800s (8h)Regular rekey prevents long-term key compromise; matches common enterprise policy
NAT TraversalEnabled (IKEv2 default)Encapsulates ESP in UDP 4500 so it passes through NAT/PAT on either side
docs Reflection · Architectural Review
What I Could Have Done Differently

An honest technical review of architectural decisions made throughout the homelab build — what works, what the alternatives were, and what would be the better choice in a production enterprise environment.

Stable Version v1.4 Updated 2026-04-25 7 min read
Network Architecture
Alternative
OSPF instead of static routes

Static routes work fine for a homelab, but OSPF (Open Shortest Path First) would dynamically learn and propagate routes between FortiGate and Cisco. If a link fails, OSPF reconverges automatically with zero manual intervention. In enterprise environments, OSPF or EIGRP is standard for any multi-router topology.

Cisco OSPF Configuration Guide
Alternative
802.1X Network Access Control

Port security with MAC limiting is a good start, but 802.1X NAC would authenticate every device with a certificate or credentials before allowing network access. A RADIUS server (FreeRADIUS or Windows NPS) rejects unconfigured devices entirely — even if they're plugged into a port.

Cisco 802.1X Guide
Alternative
FortiGate HA (Active-Passive cluster)

A single FortiGate is a single point of failure. In production, two FortiGate units run in HA mode — one active, one passive. If the primary fails, failover takes under 1 second with state synchronised (sessions preserved). Requires a second unit and a dedicated HA heartbeat link.

FortiGate HA Documentation
Alternative
Dedicated OOB Management Network

Management traffic currently shares VLAN 20 with some infrastructure services. A fully separate out-of-band (OOB) management network — physically separate NICs, its own switch ports, no routing to production VLANs — is the enterprise standard. It ensures network devices are reachable even if the production network is down.

Cisco OOB Management
Infrastructure & Virtualisation
Alternative
Terraform for Infrastructure as Code

VMs are currently provisioned with Cloud-Init and Ansible. Terraform with the Proxmox provider would declaratively define every VM — CPU, RAM, disk, network — in version-controlled HCL files. Running terraform apply creates, modifies or destroys resources. terraform plan shows changes before applying. State is tracked automatically.

Terraform Proxmox Provider
Alternative
Ceph distributed storage

Current storage is ZFS on a single Proxmox node — no redundancy. Ceph (natively supported in Proxmox) distributes data across multiple nodes with configurable replication. A 3-node Proxmox cluster with Ceph would survive a full node failure with zero data loss and zero VM downtime via live migration.

Proxmox Ceph Guide
Alternative
GitOps with ArgoCD

Current CI/CD pushes changes via Drone CI. ArgoCD implements GitOps — Kubernetes cluster state is defined in Git, and ArgoCD continuously reconciles the live cluster to match. Any drift (manual changes, failures) is automatically corrected. This is the industry standard for production Kubernetes deployments.

ArgoCD Documentation
Alternative
Vault by HashiCorp for secrets

Ansible vault and environment variables are used for secrets currently. HashiCorp Vault would centralise all secrets — API keys, certificates, SSH keys and database passwords — with fine-grained access control, audit logging, automatic rotation and dynamic credentials. Services would request secrets at runtime rather than having them stored in config files.

HashiCorp Vault Docs
Alternative
OpenTelemetry full observability

Prometheus + Grafana covers metrics well. Full observability adds distributed tracing and structured logging via OpenTelemetry — the vendor-neutral standard. Traces show exactly which service caused a slowdown. Logs, metrics and traces correlated in one view (e.g. via Grafana + Loki + Tempo stack).

OpenTelemetry Docs
Alternative
Network segmentation with micro-segmentation

VLAN-based segmentation is perimeter-based — traffic within a VLAN is unrestricted. Micro-segmentation (e.g. via VMware NSX-T or Cilium in Kubernetes) applies policy at the individual workload level. Every container or VM can only communicate with explicitly permitted peers, regardless of VLAN.

Cilium Network Policy
The homelab is intentional. Many of these "improvements" were skipped on purpose — the goal is hands-on learning, not production SLA. Building something imperfect and knowing exactly why it's imperfect is more valuable than copying a template. The items above represent the natural evolution path when this infrastructure moves toward an enterprise or production context.