Building a FortiManager Lab on Proxmox — Part 4: A Lab Edge FortiGate VM in Front of FortiManager

A FortiManager that’s directly internet-reachable is a bad idea. A FortiManager that has the same default route as your admin workstation is a worse idea. The whole point of a serious lab is to build the security boundary you’d build in production — and the obvious tool for that, on a Fortinet lab, is another Fortinet device. Part 4 builds a small FortiGate-VM that acts as the edge of the lab: gateway, firewall, NAT, FortiGuard pinhole, and the only device that touches both the lab subnets and the outside world.

By the end of this part, the topology looks like this:

                          ┌────────────────────────────┐
   home/office LAN ───────│ FGT-LAB-EDGE (FortiGate-VM)│
   (vmbr0 or vmbr-fgd)    │  port1 (wan) = DHCP        │
                          │  port2 (mgmt) 10.20.10.1   │
                          │  port3 (transit) 10.20.30.1│
                          │  port4 (cust) 10.20.40.1   │
                          └─┬──────────┬──────────┬────┘
                            │          │          │
                  vmbr-mgmt │          │ vmbr-transit
                            │          │
                       ┌────┴───┐      │
                       │ FMG    │      │
                       │ 10.20. │      │
                       │ 10.10  │      │       (Part 5 attaches managed FGTs here)
                       └────────┘      └───────────────────────────────

The lab edge FGT is the only device with a default route to the outside. FortiManager’s outbound traffic — FortiGuard, support portal, NTP, DNS — is sourced from port2 (FortiGuard egress NIC) onto vmbr-fgd, hops to the FGT, gets policy-checked, gets NAT’d, and goes out the FGT’s wan1.

Step 1 — Build the FGT-VM shell

The KVM build of FortiGate-VM follows the same shape as Part 2 — it’s a .qcow2 plus a smaller log disk, downloaded from the support portal. For the lab edge I run FortiGate-VM 7.6.x, sized small:

qm create 9100 \
   --name fgt-lab-edge \
   --memory 4096 \
   --cores 2 \
   --sockets 1 \
   --cpu host \
   --machine q35 \
   --bios seabios \
   --ostype l26 \
   --scsihw virtio-scsi-single \
   --net0 virtio,bridge=vmbr-fgd,firewall=0 \
   --net1 virtio,bridge=vmbr-mgmt,firewall=0 \
   --net2 virtio,bridge=vmbr-transit,firewall=0 \
   --net3 virtio,bridge=vmbr-cust,firewall=0 \
   --serial0 socket \
   --vga serial0

If you went with Pattern B in Part 3 (single physical NIC), substitute bridge=vmbr0 for net0. Everything else stays the same.

Import the disks:

cd /root/fgt-7.6.2
qm importdisk 9100 fortios.qcow2  local-lvm --format qcow2
qm importdisk 9100 datadrive.qcow2 local-lvm --format qcow2

qm set 9100 --scsi0 local-lvm:vm-9100-disk-0,discard=on,iothread=1,ssd=1
qm set 9100 --scsi1 local-lvm:vm-9100-disk-1,discard=on,iothread=1,ssd=1
qm set 9100 --boot order=scsi0

Note the NIC ordering matters because FortiOS maps port1 to the first virtio NIC (net0), port2 to the second, and so on. The mapping above is intentional:

Proxmox NICBridgeFortiGate portRole
net0vmbr-fgd (or vmbr0)port1WAN — only port with internet
net1vmbr-mgmtport2Management VLAN — FMG, FAZ, admin
net2vmbr-transitport3Transit to managed FGTs
net3vmbr-custport4Customer/LAN segment

If you swap the order in qm create, every CLI snippet below is wrong. Stick to the order above.

Start the VM and attach a console:

qm start 9100
qm terminal 9100

Default login admin / blank. Set a strong password immediately.

Step 2 — Address the interfaces

WAN first — DHCP from your home network on Pattern A or B:

config system interface
    edit "port1"
        set mode dhcp
        set role wan
        set allowaccess ping
        set defaultgw enable
    next
end

set defaultgw enable is the key — DHCP-learned routes only become usable as default if this is on. Once it’s up, get system interface physical will show port1 with an IP and get router info routing-table all will show a default route via the DHCP gateway.

Now the lab-internal interfaces:

config system interface
    edit "port2"
        set ip 10.20.10.1 255.255.255.0
        set role lan
        set allowaccess ping ssh https
        set description "MGMT — FMG, FAZ, admin workstation"
    next
    edit "port3"
        set ip 10.20.30.1 255.255.255.0
        set role lan
        set allowaccess ping
        set description "TRANSIT — managed FGTs WAN-side"
    next
    edit "port4"
        set ip 10.20.40.1 255.255.255.0
        set role lan
        set allowaccess ping
        set description "CUST — customer-side LAN"
    next
end

allowaccess ping ssh https on port2 only — that’s the lab management plane and the only place I want admin access on the FGT itself. The transit and customer interfaces are ping-only, which is enough for diagnostics.

Step 3 — A small DHCP server on the management segment

So the FortiManager’s port1 (and any admin workstation you drop on vmbr-mgmt) gets an address without manual config:

config system dhcp server
    edit 1
        set interface "port2"
        set default-gateway 10.20.10.1
        set netmask 255.255.255.0
        set dns-service default
        config ip-range
            edit 1
                set start-ip 10.20.10.100
                set end-ip 10.20.10.200
            next
        end
        config reserved-address
            edit 1
                set ip 10.20.10.10
                set mac AA:BB:CC:DD:EE:01
                set description "FMG-LAB-01"
            next
        end
    next
end

The reservation pins the FortiManager to 10.20.10.10 regardless of DHCP timing. Find the actual MAC with qm config 9001 | grep ^net0 — it’s after virtio=. Update the reservation accordingly.

Step 4 — Address objects and groups

Build the address objects you’ll reference in policy. Doing this once up front beats inlining IPs into every rule:

config firewall address
    edit "net-mgmt"
        set subnet 10.20.10.0 255.255.255.0
    next
    edit "net-fgd"
        set subnet 10.20.20.0 255.255.255.0
    next
    edit "net-transit"
        set subnet 10.20.30.0 255.255.255.0
    next
    edit "net-cust"
        set subnet 10.20.40.0 255.255.255.0
    next
    edit "host-fmg"
        set subnet 10.20.10.10 255.255.255.255
    next
    edit "host-admin-ws"
        set subnet 10.20.10.50 255.255.255.255
    next
end

config firewall addrgrp
    edit "lab-internal"
        set member "net-mgmt" "net-transit" "net-cust"
    next
end

The lab-internal group is what you’d allow east-west; everything else in policy is purpose-specific.

Step 5 — Policies

The whole reason for the FGT-edge is the policy set. The minimum useful set:

config firewall policy

    # 1. FMG outbound to FortiGuard / NTP / DNS / support portal
    edit 10
        set name "fmg-fortiguard-out"
        set srcintf "port2"
        set dstintf "port1"
        set srcaddr "host-fmg"
        set dstaddr "all"
        set action accept
        set service "DNS" "HTTPS" "FortiGuard-Web-Filter" "FortiGuard-AntiSpam" "PING" "NTP"
        set schedule "always"
        set logtraffic all
        set nat enable
        set comments "FMG -> internet, scoped to required services"
    next

    # 2. Admin workstation -> FMG GUI
    edit 20
        set name "admin-to-fmg"
        set srcintf "port2"
        set dstintf "port2"
        set srcaddr "host-admin-ws"
        set dstaddr "host-fmg"
        set action accept
        set service "HTTPS" "SSH" "PING"
        set schedule "always"
        set logtraffic all
    next

    # 3. Managed FGTs (transit) -> FMG (mgmt) for registration / FGFM
    edit 30
        set name "managed-fgt-to-fmg"
        set srcintf "port3"
        set dstintf "port2"
        set srcaddr "net-transit"
        set dstaddr "host-fmg"
        set action accept
        set service "HTTPS" "PING"
        set service "FGFM"
        set schedule "always"
        set logtraffic all
        set comments "FGFM = FortiGate-FortiManager protocol on TCP/541"
    next

    # 4. Managed FGTs -> internet (FortiGuard, NTP)
    edit 40
        set name "managed-fgt-out"
        set srcintf "port3"
        set dstintf "port1"
        set srcaddr "net-transit"
        set dstaddr "all"
        set action accept
        set service "DNS" "HTTPS" "PING" "NTP"
        set schedule "always"
        set logtraffic all
        set nat enable
    next

    # 5. Customer LAN -> internet (everything)
    edit 50
        set name "cust-out"
        set srcintf "port4"
        set dstintf "port1"
        set srcaddr "net-cust"
        set dstaddr "all"
        set action accept
        set service "ALL"
        set schedule "always"
        set logtraffic all
        set nat enable
        set utm-status enable
        set ssl-ssh-profile "certificate-inspection"
        set webfilter-profile "default"
    next

    # 6. Implicit deny (always log denies in a lab — debugging gold)
    edit 99
        set name "log-everything-else"
        set srcintf "any"
        set dstintf "any"
        set srcaddr "all"
        set dstaddr "all"
        set action deny
        set service "ALL"
        set schedule "always"
        set logtraffic all
    next

end

Read it from the top:

  • Rule 10 is the FortiGuard pinhole. Note host-fmg as the source — only the FortiManager can use this rule. A compromised admin workstation can’t piggy-back. The service list is scoped: DNS, HTTPS, NTP, ICMP, plus the FortiGuard-specific service objects FortiOS ships out of the box. No “service ALL” here — if FMG suddenly tries to talk to something on a non-standard port, it should fail loudly.
  • Rule 20 is intra-segment, on the same interface, but listed explicitly. Without it, intra-VLAN traffic on port2 between admin workstation and FMG is fine without policy on most FortiOS versions (intra-zone traffic permitted by default), but I prefer to make it explicit and logged. Set intra-zone deny on the zone if you want zone-based enforcement, but for a lab the explicit policy is fine.
  • Rule 30 is the FGFM registration path. TCP/541 is the FortiGate-FortiManager protocol that handles registration, config sync, and policy installs. The Fortinet-supplied service object is named FGFM. Without this rule, managed FGTs in the transit segment can’t talk to FMG.
  • Rule 40 lets the managed FGTs themselves reach FortiGuard. They need their own update path.
  • Rule 50 is the customer LAN’s general internet egress with UTM enabled. Useful for testing FortiGuard categorisation behaviour from FMG-pushed profiles in Part 5.
  • Rule 99 is the explicit deny with logging. Implicit deny exists, but FortiOS doesn’t log it by default. In a lab, log everything — denied traffic is the most useful signal you have when something doesn’t work.

Step 6 — What FortiManager actually needs to reach

A FortiManager doing real work talks to a surprising number of external services. The list, with the destinations the lab edge needs to permit:

ServiceDestinationPortRequired for
FortiGuard updatesupdate.fortiguard.netTCP/443, TCP/8890AV / IPS / web-filter signature pulls
FortiGuard rating*.fortiguard.netTCP/443URL category lookups (GUI testing)
FortiCloud / supportsupport.fortinet.com, forticloud.comTCP/443Licence activation, contract checks
DNS1.1.1.1 / your local resolverUDP/53, TCP/53All of the above
NTPpool.ntp.orgUDP/123Clock sync (TLS to CDN fails on drift)

All HTTPS-out, no inbound. The FortiGuard “service” objects in FortiOS already encode the right ports — Rule 10 above covers the lot when you include FortiGuard-Web-Filter, FortiGuard-AntiSpam, HTTPS, DNS, NTP. If you want belt-and-braces visibility, swap the service group for a single ALL while you’re stabilising and switch to scoped services once you’ve watched the FortiView traffic for a session.

Step 7 — Anti-rules (the things that should not work)

Equally important. Build the rules that prove the boundary exists:

  • Anything on vmbr-fgd should NOT reach FMG. The FortiGuard egress NIC of the FortiManager (port2 in the FMG, on vmbr-fgd) is outbound-only by design.
  • Anything on vmbr-cust should NOT reach FMG or transit. Customer LAN can reach the internet, nothing else.
  • port1 (WAN) should NOT have inbound rules. The lab is outbound-only. If you ever need to test inbound, put a VIP on the WAN with explicit destination (an internal-only test endpoint) — never expose the FMG GUI to the WAN side.

You can prove these by enabling Rule 99 (the deny-with-log) and watching Log & Report → Forward Traffic — every blocked attempt shows up. On a clean lab the only traffic in the deny log should be background broadcast / multicast that never leaves the box.

Step 8 — Local-in policy on the lab edge

The FGT itself is reachable on port2 for admin. Lock that down with a local-in policy so the management VLAN can talk to it but nothing else can:

config firewall local-in-policy
    edit 1
        set intf "port2"
        set srcaddr "host-admin-ws" "host-fmg"
        set dstaddr "all"
        set action accept
        set service "HTTPS" "SSH" "PING"
        set schedule "always"
    next
    edit 99
        set intf "any"
        set srcaddr "all"
        set dstaddr "all"
        set action deny
        set service "HTTPS" "SSH"
        set schedule "always"
    next
end

That stops port1 (WAN) from being a management plane even if a stray allowaccess slips through. On a lab, a single set allowaccess ping ssh https on port1 “for convenience” is the most common mistake.

Step 9 — Verification

Five things worth proving end-to-end before Part 5:

# 1. FGT-edge has internet
exec ping 1.1.1.1
exec ping update.fortiguard.net

# 2. FortiGuard contract registered
diagnose autoupdate versions
get system fortiguard

# 3. FMG can reach internet through FGT-edge
#    (run from FMG console)
exec ping 1.1.1.1
exec ping update.fortiguard.net
diagnose test application updated 1

# 4. Admin workstation can reach FMG GUI but not the FGT-edge WAN
curl -k https://10.20.10.10                     # FMG GUI
curl -k --max-time 3 https://<wan-ip>           # should fail (local-in deny)

# 5. The deny log is empty of unexpected traffic
diagnose log test                                # generates a few sample log entries
exec log filter category 0
exec log display

The shape of the verification is the boundary itself: FMG can reach the internet, admins can reach FMG, and nothing else works. That’s exactly the property the lab edge exists to provide.

Step 10 — Snapshot

Before Part 5 attaches managed FGTs and starts changing things:

qm snapshot 9100 edge-baseline --description "FGT-edge built, policies stable, FMG reachable, no managed FGTs yet"
qm snapshot 9001 fmg-pre-managed --description "FMG behind FGT-edge, FortiGuard live, no managed devices yet"

What’s next

Part 5 is the payoff: build two managed FortiGate-VMs on vmbr-transit, register them to the FortiManager via FGFM through the lab edge’s Rule 30, create an ADOM, build a policy package, and push an install. From that point you have a fully working integration lab — break it, snapshot it, rebuild it, and learn the operational rhythm of central management without touching anything in production.

The single-line summary for Part 4: FortiGate-VM as the lab edge, four NICs mapped to the four bridges, policy set scoped to exactly what FMG needs and nothing more, local-in policy locking down the FGT itself, deny-with-log catching anything else.