Most SD-WAN designs quietly flatten your network segmentation the moment traffic hits the overlay. Here's how to carry three VRFs end to end across a FortiGate fabric — with the full CLI for a single branch and hub.
Segmentation is easy to get right on the LAN and easy to lose on the WAN. You spend real effort keeping corporate, guest, and IoT traffic in separate VRFs at every site — and then a flat SD-WAN overlay picks it all up, drops it into one tunnel, and hands it to the hub as a single undifferentiated flow. The isolation you designed ends at the firewall's LAN interface. Anything past it is trust-by-accident.
VRF-aware SD-WAN fixes that. The principle is simple: each VRF keeps its own overlay across the WAN, so segmentation is continuous from branch LAN to hub LAN, while SD-WAN still does its job — measuring each path and steering that VRF's traffic onto the best one. You get per-segment isolation and per-segment path selection, at the same time.
This walkthrough builds it end to end on FortiGate: one branch, one hub, three VRFs on each side, dual internet underlays. Everything below is CLI — it's faster to read, faster to paste, and it makes the structure obvious.
There is no single tunnel that magically understands VRFs. On FortiGate, the clean, supportable way to keep segments isolated across the WAN is one overlay per VRF, per underlay. The underlays themselves stay in the global routing table (VRF 0) — they only need to reach the far end's public IPs. Each IPsec tunnel interface is then dropped into the VRF it serves. Because a tunnel interface belongs to exactly one VRF, traffic that enters it can never leak into another segment: the routing table it consults is the VRF's, not the global one.
SD-WAN sits on top. Each VRF's pair of overlays becomes an SD-WAN zone; a health check probes the far end inside that VRF; and an SD-WAN rule steers the segment's traffic onto whichever path currently meets the SLA. Guest traffic can ride the cheapest link, IoT can pin to the most stable one, corporate can load-share — all independently, all without any of them seeing each other.
| Element | Branch FGT | Hub FGT |
|---|---|---|
| Underlay 1 (port1) | 203.0.113.10/30 → gw .9 | 192.0.2.10/30 → gw .9 |
| Underlay 2 (port2) | 198.51.100.10/30 → gw .9 | 192.0.2.14/30 → gw .13 |
| VRF 101 · CORP LAN (port10) | 172.16.101.1/24 | 10.1.11.1/30 |
| VRF 201 · GUEST LAN (port11) | 192.168.201.1/24 | 10.1.12.1/30 |
| VRF 140 · IOT LAN (port12) | 10.10.140.1/24 | 10.1.13.1/30 |
| CORP overlays (VRF 101) | .2 → 10.254.1.2 / 10.254.2.2 | .1 → 10.254.1.1 / 10.254.2.1 |
| GUEST overlays (VRF 201) | .2 → 10.254.3.2 / 10.254.4.2 | .1 → 10.254.3.1 / 10.254.4.1 |
| IOT overlays (VRF 140) | .2 → 10.254.5.2 / 10.254.6.2 | .1 → 10.254.5.1 / 10.254.6.1 |
Documentation IPs throughout (RFC 5737 / RFC 1918). Overlay tunnel interfaces use /32 point-to-point addressing; the hub always takes .1, the branch .2.
Work outside-in: transport first, then the segments, then the overlays that stitch them together, then the SD-WAN logic that steers them.
# The underlays only need to reach the hub's public IPs.
config system interface
edit "port1"
set alias "INET1"
set role wan
set ip 203.0.113.10 255.255.255.252
set allowaccess ping
next
edit "port2"
set alias "INET2"
set role wan
set ip 198.51.100.10 255.255.255.252
set allowaccess ping
next
end
config router static
edit 1
set gateway 203.0.113.9
set device "port1"
set comment "default via INET1"
next
edit 2
set gateway 198.51.100.9
set device "port2"
set comment "default via INET2"
next
end
# Each LAN interface is placed into its own VRF. This is the
# segmentation boundary the overlays will extend across the WAN.
config system interface
edit "port10"
set alias "CORP-LAN"
set vrf 101
set ip 172.16.101.1 255.255.255.0
set allowaccess ping
next
edit "port11"
set alias "GUEST-LAN"
set vrf 201
set ip 192.168.201.1 255.255.255.0
set allowaccess ping
next
edit "port12"
set alias "IOT-LAN"
set vrf 140
set ip 10.10.140.1 255.255.255.0
set allowaccess ping
next
end
# 6 tunnels: {CORP,GUEST,IOT} x {INET1,INET2}. IKEv2, route-based.
config vpn ipsec phase1-interface
edit "OV-CORP1"
set interface "port1"
set ike-version 2
set peertype any
set net-device disable
set proposal aes256gcm-prfsha384
set dhgrp 20
set remote-gw 192.0.2.10
set psksecret <CORP1-PSK>
next
edit "OV-CORP2"
set interface "port2"
set ike-version 2
set peertype any
set net-device disable
set proposal aes256gcm-prfsha384
set dhgrp 20
set remote-gw 192.0.2.14
set psksecret <CORP2-PSK>
next
edit "OV-GUEST1"
set interface "port1"
set ike-version 2
set peertype any
set net-device disable
set proposal aes256gcm-prfsha384
set dhgrp 20
set remote-gw 192.0.2.10
set psksecret <GUEST1-PSK>
next
edit "OV-GUEST2"
set interface "port2"
set ike-version 2
set peertype any
set net-device disable
set proposal aes256gcm-prfsha384
set dhgrp 20
set remote-gw 192.0.2.14
set psksecret <GUEST2-PSK>
next
edit "OV-IOT1"
set interface "port1"
set ike-version 2
set peertype any
set net-device disable
set proposal aes256gcm-prfsha384
set dhgrp 20
set remote-gw 192.0.2.10
set psksecret <IOT1-PSK>
next
edit "OV-IOT2"
set interface "port2"
set ike-version 2
set peertype any
set net-device disable
set proposal aes256gcm-prfsha384
set dhgrp 20
set remote-gw 192.0.2.14
set psksecret <IOT2-PSK>
next
end
config vpn ipsec phase2-interface
edit "OV-CORP1"
set phase1name "OV-CORP1"
set proposal aes256gcm
next
edit "OV-CORP2"
set phase1name "OV-CORP2"
set proposal aes256gcm
next
edit "OV-GUEST1"
set phase1name "OV-GUEST1"
set proposal aes256gcm
next
edit "OV-GUEST2"
set phase1name "OV-GUEST2"
set proposal aes256gcm
next
edit "OV-IOT1"
set phase1name "OV-IOT1"
set proposal aes256gcm
next
edit "OV-IOT2"
set phase1name "OV-IOT2"
set proposal aes256gcm
next
end
# This single 'set vrf' per tunnel is what makes the overlay
# VRF-aware. CORP tunnels -> VRF 101, GUEST -> 201, IOT -> 140.
config system interface
edit "OV-CORP1"
set vrf 101
set ip 10.254.1.2 255.255.255.255
set remote-ip 10.254.1.1 255.255.255.255
set allowaccess ping
next
edit "OV-CORP2"
set vrf 101
set ip 10.254.2.2 255.255.255.255
set remote-ip 10.254.2.1 255.255.255.255
set allowaccess ping
next
edit "OV-GUEST1"
set vrf 201
set ip 10.254.3.2 255.255.255.255
set remote-ip 10.254.3.1 255.255.255.255
set allowaccess ping
next
edit "OV-GUEST2"
set vrf 201
set ip 10.254.4.2 255.255.255.255
set remote-ip 10.254.4.1 255.255.255.255
set allowaccess ping
next
edit "OV-IOT1"
set vrf 140
set ip 10.254.5.2 255.255.255.255
set remote-ip 10.254.5.1 255.255.255.255
set allowaccess ping
next
edit "OV-IOT2"
set vrf 140
set ip 10.254.6.2 255.255.255.255
set remote-ip 10.254.6.1 255.255.255.255
set allowaccess ping
next
end
config system sdwan
set status enable
# One zone per VRF; members within a zone share that VRF.
config zone
edit "OV-CORP"
next
edit "OV-GUEST"
next
edit "OV-IOT"
next
end
config members
edit 1
set interface "OV-CORP1"
set zone "OV-CORP"
next
edit 2
set interface "OV-CORP2"
set zone "OV-CORP"
next
edit 3
set interface "OV-GUEST1"
set zone "OV-GUEST"
next
edit 4
set interface "OV-GUEST2"
set zone "OV-GUEST"
next
edit 5
set interface "OV-IOT1"
set zone "OV-IOT"
next
edit 6
set interface "OV-IOT2"
set zone "OV-IOT"
next
end
# Each health check probes the hub overlay IP inside that VRF,
# so the SLA reflects the segment's real path — not a shared one.
config health-check
edit "HC-CORP"
set server "10.254.1.1"
set members 1 2
config sla
edit 1
set latency-threshold 150
set packetloss-threshold 2
next
end
next
edit "HC-GUEST"
set server "10.254.3.1"
set members 3 4
config sla
edit 1
set latency-threshold 100
set jitter-threshold 30
set packetloss-threshold 1
next
end
next
edit "HC-IOT"
set server "10.254.5.1"
set members 5 6
config sla
edit 1
set latency-threshold 150
set packetloss-threshold 1
next
end
next
end
config service
edit 1
set name "CORP"
set mode sla
set src "CORP-LAN"
set dst "HUB-CORP-LAN"
config sla
edit "HC-CORP"
set id 1
next
end
set priority-members 1 2
next
edit 2
set name "GUEST"
set mode sla
set src "GUEST-LAN"
set dst "HUB-GUEST-LAN"
config sla
edit "HC-GUEST"
set id 1
next
end
set priority-members 3 4
next
edit 3
set name "IOT"
set mode sla
set src "IOT-LAN"
set dst "HUB-IOT-LAN"
config sla
edit "HC-IOT"
set id 1
next
end
set priority-members 5 6
next
end
end
config firewall address
edit "CORP-LAN"
set subnet 172.16.101.0 255.255.255.0
next
edit "GUEST-LAN"
set subnet 192.168.201.0 255.255.255.0
next
edit "IOT-LAN"
set subnet 10.10.140.0 255.255.255.0
next
edit "HUB-CORP-LAN"
set subnet 10.1.11.0 255.255.255.252
next
edit "HUB-GUEST-LAN"
set subnet 10.1.12.0 255.255.255.252
next
edit "HUB-IOT-LAN"
set subnet 10.1.13.0 255.255.255.252
next
end
# Route to each hub segment via its SD-WAN zone. The route
# installs in the VRF of the zone's members — no 'set vrf' needed here.
config router static
edit 10
set dst 10.1.11.0 255.255.255.252
set sdwan-zone "OV-CORP"
next
edit 20
set dst 10.1.12.0 255.255.255.252
set sdwan-zone "OV-GUEST"
next
edit 30
set dst 10.1.13.0 255.255.255.252
set sdwan-zone "OV-IOT"
next
end
# srcintf and dstintf sit in the same VRF, so the policy can only
# ever permit traffic within one segment. Cross-VRF is impossible
# here unless you deliberately add inter-VRF routing + policy.
config firewall policy
edit 1
set name "CORP-to-HUB"
set srcintf "port10"
set dstintf "OV-CORP"
set srcaddr "CORP-LAN"
set dstaddr "HUB-CORP-LAN"
set action accept
set schedule "always"
set service "ALL"
set logtraffic all
next
edit 2
set name "GUEST-to-HUB"
set srcintf "port11"
set dstintf "OV-GUEST"
set srcaddr "GUEST-LAN"
set dstaddr "HUB-GUEST-LAN"
set action accept
set schedule "always"
set service "ALL"
set logtraffic all
next
edit 3
set name "IOT-to-HUB"
set srcintf "port12"
set dstintf "OV-IOT"
set srcaddr "IOT-LAN"
set dstaddr "HUB-IOT-LAN"
set action accept
set schedule "always"
set service "ALL"
set logtraffic all
next
end
The hub is a mirror image. The two structural differences: its overlays point back at the branch's two public IPs, and it takes the .1 address on every overlay. With a single branch we terminate the tunnels statically; the "Scaling up" note covers the dial-up / ADVPN variant for many spokes.
config system interface
edit "port1"
set alias "INET1"
set role wan
set ip 192.0.2.10 255.255.255.252
set allowaccess ping
next
edit "port2"
set alias "INET2"
set role wan
set ip 192.0.2.14 255.255.255.252
set allowaccess ping
next
end
config router static
edit 1
set gateway 192.0.2.9
set device "port1"
next
edit 2
set gateway 192.0.2.13
set device "port2"
next
end
config system interface
edit "port10"
set alias "CORP-DC"
set vrf 101
set ip 10.1.11.1 255.255.255.252
set allowaccess ping
next
edit "port11"
set alias "GUEST-DC"
set vrf 201
set ip 10.1.12.1 255.255.255.252
set allowaccess ping
next
edit "port12"
set alias "IOT-DC"
set vrf 140
set ip 10.1.13.1 255.255.255.252
set allowaccess ping
next
end
config vpn ipsec phase1-interface
edit "OV-CORP1"
set interface "port1"
set ike-version 2
set peertype any
set net-device disable
set proposal aes256gcm-prfsha384
set dhgrp 20
set remote-gw 203.0.113.10
set psksecret <CORP1-PSK>
next
edit "OV-CORP2"
set interface "port2"
set ike-version 2
set peertype any
set net-device disable
set proposal aes256gcm-prfsha384
set dhgrp 20
set remote-gw 198.51.100.10
set psksecret <CORP2-PSK>
next
edit "OV-GUEST1"
set interface "port1"
set ike-version 2
set peertype any
set net-device disable
set proposal aes256gcm-prfsha384
set dhgrp 20
set remote-gw 203.0.113.10
set psksecret <GUEST1-PSK>
next
edit "OV-GUEST2"
set interface "port2"
set ike-version 2
set peertype any
set net-device disable
set proposal aes256gcm-prfsha384
set dhgrp 20
set remote-gw 198.51.100.10
set psksecret <GUEST2-PSK>
next
edit "OV-IOT1"
set interface "port1"
set ike-version 2
set peertype any
set net-device disable
set proposal aes256gcm-prfsha384
set dhgrp 20
set remote-gw 203.0.113.10
set psksecret <IOT1-PSK>
next
edit "OV-IOT2"
set interface "port2"
set ike-version 2
set peertype any
set net-device disable
set proposal aes256gcm-prfsha384
set dhgrp 20
set remote-gw 198.51.100.10
set psksecret <IOT2-PSK>
next
end
config vpn ipsec phase2-interface
edit "OV-CORP1"
set phase1name "OV-CORP1"
set proposal aes256gcm
next
edit "OV-CORP2"
set phase1name "OV-CORP2"
set proposal aes256gcm
next
edit "OV-GUEST1"
set phase1name "OV-GUEST1"
set proposal aes256gcm
next
edit "OV-GUEST2"
set phase1name "OV-GUEST2"
set proposal aes256gcm
next
edit "OV-IOT1"
set phase1name "OV-IOT1"
set proposal aes256gcm
next
edit "OV-IOT2"
set phase1name "OV-IOT2"
set proposal aes256gcm
next
end
config system interface
edit "OV-CORP1"
set vrf 101
set ip 10.254.1.1 255.255.255.255
set remote-ip 10.254.1.2 255.255.255.255
set allowaccess ping
next
edit "OV-CORP2"
set vrf 101
set ip 10.254.2.1 255.255.255.255
set remote-ip 10.254.2.2 255.255.255.255
set allowaccess ping
next
edit "OV-GUEST1"
set vrf 201
set ip 10.254.3.1 255.255.255.255
set remote-ip 10.254.3.2 255.255.255.255
set allowaccess ping
next
edit "OV-GUEST2"
set vrf 201
set ip 10.254.4.1 255.255.255.255
set remote-ip 10.254.4.2 255.255.255.255
set allowaccess ping
next
edit "OV-IOT1"
set vrf 140
set ip 10.254.5.1 255.255.255.255
set remote-ip 10.254.5.2 255.255.255.255
set allowaccess ping
next
edit "OV-IOT2"
set vrf 140
set ip 10.254.6.1 255.255.255.255
set remote-ip 10.254.6.2 255.255.255.255
set allowaccess ping
next
end
config system sdwan
set status enable
config zone
edit "OV-CORP"
next
edit "OV-GUEST"
next
edit "OV-IOT"
next
end
config members
edit 1
set interface "OV-CORP1"
set zone "OV-CORP"
next
edit 2
set interface "OV-CORP2"
set zone "OV-CORP"
next
edit 3
set interface "OV-GUEST1"
set zone "OV-GUEST"
next
edit 4
set interface "OV-GUEST2"
set zone "OV-GUEST"
next
edit 5
set interface "OV-IOT1"
set zone "OV-IOT"
next
edit 6
set interface "OV-IOT2"
set zone "OV-IOT"
next
end
config health-check
edit "HC-CORP"
set server "10.254.1.2"
set members 1 2
config sla
edit 1
set latency-threshold 150
set packetloss-threshold 2
next
end
next
edit "HC-GUEST"
set server "10.254.3.2"
set members 3 4
config sla
edit 1
set latency-threshold 100
set jitter-threshold 30
set packetloss-threshold 1
next
end
next
edit "HC-IOT"
set server "10.254.5.2"
set members 5 6
config sla
edit 1
set latency-threshold 150
set packetloss-threshold 1
next
end
next
end
config service
edit 1
set name "CORP"
set mode sla
set src "HUB-CORP-LAN"
set dst "CORP-LAN"
config sla
edit "HC-CORP"
set id 1
next
end
set priority-members 1 2
next
edit 2
set name "GUEST"
set mode sla
set src "HUB-GUEST-LAN"
set dst "GUEST-LAN"
config sla
edit "HC-GUEST"
set id 1
next
end
set priority-members 3 4
next
edit 3
set name "IOT"
set mode sla
set src "HUB-IOT-LAN"
set dst "IOT-LAN"
config sla
edit "HC-IOT"
set id 1
next
end
set priority-members 5 6
next
end
end
config firewall address
edit "CORP-LAN"
set subnet 172.16.101.0 255.255.255.0
next
edit "GUEST-LAN"
set subnet 192.168.201.0 255.255.255.0
next
edit "IOT-LAN"
set subnet 10.10.140.0 255.255.255.0
next
edit "HUB-CORP-LAN"
set subnet 10.1.11.0 255.255.255.252
next
edit "HUB-GUEST-LAN"
set subnet 10.1.12.0 255.255.255.252
next
edit "HUB-IOT-LAN"
set subnet 10.1.13.0 255.255.255.252
next
end
# Routes back toward the branch segments, via each SD-WAN zone.
config router static
edit 10
set dst 172.16.101.0 255.255.255.0
set sdwan-zone "OV-CORP"
next
edit 20
set dst 192.168.201.0 255.255.255.0
set sdwan-zone "OV-GUEST"
next
edit 30
set dst 10.10.140.0 255.255.255.0
set sdwan-zone "OV-IOT"
next
end
config firewall policy
edit 1
set name "HUB-to-CORP"
set srcintf "port10"
set dstintf "OV-CORP"
set srcaddr "HUB-CORP-LAN"
set dstaddr "CORP-LAN"
set action accept
set schedule "always"
set service "ALL"
set logtraffic all
next
edit 2
set name "HUB-to-GUEST"
set srcintf "port11"
set dstintf "OV-GUEST"
set srcaddr "HUB-GUEST-LAN"
set dstaddr "GUEST-LAN"
set action accept
set schedule "always"
set service "ALL"
set logtraffic all
next
edit 3
set name "HUB-to-IOT"
set srcintf "port12"
set dstintf "OV-IOT"
set srcaddr "HUB-IOT-LAN"
set dstaddr "IOT-LAN"
set action accept
set schedule "always"
set service "ALL"
set logtraffic all
next
end
Two things need to be true: every overlay is up in the right VRF, and each VRF's route table only knows about its own segment. These commands prove both.
# All six overlays should show status=up.
diagnose vpn tunnel list | grep -e name -e status
# Per-VRF routing tables — CORP should see 10.1.11.0/30 and
# nothing from GUEST or IOT. Repeat for vrf 20 and 30.
get router info routing-table all
get router info vrf 101 routing-table
# SD-WAN health per VRF: latency, jitter, loss, and which member
# is currently preferred for each service.
diagnose sys sdwan health-check status
diagnose sys sdwan service
# The real test: from a CORP host you can reach 10.1.11.0/30,
# but a route to 10.1.12.0/30 (GUEST) simply does not exist.
If a VRF's routing table shows a segment it shouldn't, the leak is almost always a tunnel interface missing its set vrf, or a static route pointing at a zone whose members landed in the wrong VRF. Both are visible in the output above before a single packet is sent.
The lab assumes each segment is directly connected to the firewall. Often it isn't — the segment is a prefix (or several) behind a downstream L3 switch or router that hands off to the FortiGate on a routed link inside that VRF. Nothing about the overlay design changes; you just teach the VRF where the local prefixes are, and advertise them across.
set vrf 140), then either add a VRF-scoped static route toward the downstream prefix, or run BGP/OSPF with the downstream device inside that VRF. Add the remote prefix as an address object and reference it in the SD-WAN service dst and the firewall policy, exactly as we did with the directly-connected subnets. The overlay carries whatever the VRF's route table contains — one subnet or fifty.
Two branches you can still build by hand. Twenty, you shouldn't. The design holds, but the mechanics change:
Go dial-up + ADVPN. Convert the hub phase1s to dial-up (type dynamic) so one template accepts every spoke per VRF, and enable ADVPN so spokes build shortcuts to each other on demand — without the hub brokering every packet. You keep one overlay per VRF; you stop keeping one per spoke on the hub.
Let BGP carry the segments. Replace the per-VRF static routes with BGP per VRF over the overlays. New sites and new prefixes propagate on their own, and SD-WAN still owns path selection — BGP decides reachability, SD-WAN decides which reachable path wins.
Consider a single overlay with EVPN. Where the overlay count genuinely hurts, VXLAN/EVPN over IPsec carries many VRFs inside one tunnel using VNIs — the FortiGate equivalent of MPLS L3VPN's per-VRF labels. It's more moving parts up front, and the right call only once "one overlay per VRF" stops scaling for you.
Whichever way you scale, the invariant is the one this post is built around: a segment that is isolated on the LAN stays isolated across the WAN. If your current SD-WAN can't say that, the overlay is quietly undoing your segmentation — and that's worth fixing before it's the finding in someone's audit.
We design and build VRF-aware SD-WAN fabrics on FortiGate, Palo Alto, and Cisco — including ADVPN, BGP-per-VRF, and EVPN overlays. A network health check is a good first step: we'll map your segments, your paths, and where the overlay is currently flattening them.