T Techclick ← FortiGate hub
FortiGate · FortiOS 7.4 · Session factory

FortiGate is a session factory. First packet vs existing session.

Ticket INC-4418: “Finance cannot open SaaS. Policy 14 is already Accept — please add policy 15.” The browser sits on connecting. On FGT-LAB a session already exists in VDOM root. proto_state=01. That is SYN seen, not established. The factory printed a row. Adding another allow does not finish TCP.

20 min read · L2 primary · Quiz at end · Dummy lab only

After this page you can

Quick answer

A FortiGate VDOM is a stateful session factory. Official: FortiOS inspects a sequential group of packets as one session instead of judging every packet alone. The first packet of a new 5-tuple walks routing, then policy lookup (iprope), then NAT, then session install. Later packets of the same 5-tuple hit the session table and skip that lookup. Success is a live row with policy_id matching iprope and proto_state=11 — not another Accept on the policy list.

Say this out loud

I state VDOM and HA role first. Then I ask: is there already a session for this 5-tuple? If yes, I read proto_state and the stamps on that row. If no, I run Policy Lookup / iprope. I do not add Accept rules to fix a one-way handshake, and I do not expect a policy edit to rewrite a session that already exists.

Hero · one session, not a stack of blades
Teaches: user traffic becomes one FortiOS session row, not a pile of UTM features
Notice: the box is a session factory. Antivirus, IPS and SSL inspect are stamps on that session after policy match.

1. Why the session table is the product

FortiGate interviews die when the student lists UTM blades. Production tickets die the same way. Helpdesk sees Policy & Objects → Firewall Policy action Accept and assumes the path is done. Accept only means “this 5-tuple is allowed to become a session.” It does not mean SYN-ACK came back. It does not mean the next packet will walk the policy list again.

Official session overview: sessions let FortiOS act on a sequential group of packets together. An entry is placed in the session table for each traffic session that passes a security policy. GUI view: Dashboard → FortiView Sessions (needs a logging device and traffic log). CLI truth for a hang: diagnose sys session filter then diagnose sys session list. The fields that close INC-4418 are vd, proto_state, policy_id, and the SNAT hooks.

Concept

One VDOM, one ingress interface, one first-packet policy lookup, optional SNAT, one session row.

Path

Packet in → session lookup. Miss = install. Hit = ride the existing row. Policy list is not re-walked on every packet.

Do

State VDOM. Filter the lab source 10.10.20.45. List the session. Compare iprope. Then decide isolate vs change-control.

Lab box only

Hostname FGT-LAB, serial FGTVM0LAB000001, FortiOS 7.4.5 build 2702. RFC 5737 addresses. No live tenant IDs.

The lie on the ticket

“Policy 14 is Accept, so we need a new policy.” Accept plus proto_state=01 is a path problem, not a missing allow. Policy 15 will not finish TCP. The factory already printed the row.

2. Mental model — VDOM, lookup, row

Hold three parts. Interviews fail when people mix them.

1. The factory is the VDOM

Each VDOM is its own virtual firewall: own interfaces, own routing, own policy list, own session table. Default management VDOM is root. Traffic in CustA is invisible from root.

2. The hiring manager is policy lookup

First packet: ingress zone → dest interface from routing → first-match firewall policy (iprope). GUI: Policy & Objects → Firewall Policy → Policy Lookup. CLI: diagnose firewall iprope lookup.

3. The ticket is the session row

After accept, FortiOS installs orig-tuple + reply-tuple, NAT hooks, policy_id, timeout. Later packets match that row. Helpers and NPU offload change what you see, not the story.

4. Proof is diagnose, not the GUI checkbox

FortiView Sessions is history plus live when logging works. diagnose sys session list is the live table. iprope is predicted. Those three disagree on purpose — that is the ticket.

Path · six stations on first packet
Teaches: VDOM then zone then policy then SNAT then SD-WAN then session
Read left to right. Those stations run on the first packet of a new 5-tuple. Existing sessions skip the middle of this line.
Flow 1 · factory stamps on one session
First packet in this VDOM → five stamps → session row 1 VDOM root / CustA 2 Zone / IF port2 → LAN 3 Policy iprope → id=14 4 SNAT 203.0.113.20 5 Session row proto_state · policy_id Lab tuple (dummy · RFC 5737) 10.10.20.45:51990 → 198.51.100.80:443 · SNAT 203.0.113.20:18422 · policy 14 · vd=0 (root) UTM blades sit on the session after policy match. They are not the factory.

Read left → right. If you skip VDOM, the empty session table is a lie you told yourself.

Pre-train the hard words

5-tuple = src IP, dst IP, src port, dst port, protocol — plus the VDOM that owns the table. iprope is FortiOS policy lookup (predicted). proto_state is handshake language: lab teaching value 01 = SYN seen / incomplete; official Admin Guide sample for a live HTTPS session is 11 (established). vd on the session row is the VDOM index. Policy Lookup in the GUI is the same predicted match as iprope, not the live table.

3. First packet vs existing session

The diamond is not “is Accept checked.” The diamond is “does this 5-tuple already have a session in this VDOM?” That is why “I added a rule” sometimes changes nothing until the old row dies.

Decision · first packet or existing row
Teaches: a diamond splits first-packet policy lookup from later-packet session-table hit
Notice: the split is not allow/deny. It is “session already exists?”
Flow 2 · first packet setup vs ride the table
Ingress → session lookup → install or ride 1 Ingress port2 · VDOM Session? 5-tuple + vd no FIRST PACKET — print a new row this 5-tuple is new in this VDOM Route / dest IF outgoing zone Policy lookup iprope first-match NAT + profiles SNAT / VIP / UTM Session install orig + reply tuples · policy_id EXISTING SESSION — later packets of the same 5-tuple skip policy list · refresh timeout · apply the stamps already on the row · egress yes → ride table Facts students invert 1. Policy Lookup / iprope is predicted. diagnose sys session list is live. They can disagree. 2. Editing policy 14 does not rewrite a row that already exists. Clear that tuple, or wait for timeout. 3. proto_state=01 after 40+ seconds is SYN without SYN-ACK — not a missing Accept. 4. Empty table on the HA standby or in the wrong VDOM is self-inflicted. State role + vd first. Sources: FortiOS Admin Guide · Using a session table · Configuring a firewall policy · diagnose firewall iprope lookup

Read the diamond first. Left/green = already a session. Top bar = first packet. Bottom box = the four traps this page exists to kill.

Pipeline · packet → lookup → row → reply
Teaches: packet in, policy lookup, session row, reply
Notice: reply traffic matches the session’s reply-tuple. It does not need a second Accept.
#1 student trap — I added a rule and nothing changed

The first packet already printed a row. Later packets ride that row with the old policy_id, old SNAT, old outgoing interface. iprope now says the new policy. The live list still says 14. That is not FortiOS being broken. Clear only the filtered lab tuple after change-control — never diagnose sys session clear with no filter on a production box.

4. How to choose the next proof

If X then Y. Do not mix predicted match with a live row, and do not mix “member is up” with “session is established.”

SituationPreferDo not
Browser connecting, policy already AcceptFilter + diagnose sys session list. Read proto_state before you clone a policy.Add policy 15 Any-Any “just for the ticket.”
Need to know which policy should hitGUI Policy Lookup or diagnose firewall iprope lookupTreat iprope as proof the handshake finished.
Just edited NAT / outgoing IF / profileFiltered session clear of that 5-tuple, then one new first packetExpect existing rows to pick up the edit.
Empty session table after failoverget system status + get system ha status + VDOM selectorClone every root policy into the customer VDOM.
MSSP / multi-tenant boxSwitch VDOM (top bar or config vdom / edit CustA) before any session commandReading root sessions for traffic that lives in CustA.
Need packet-by-packet storyFiltered diagnose debug flow, then disable + resetUnfiltered flow left on overnight.
Source for the choose table

Firewall Policy and Policy Lookup: FortiOS Administration Guide — Configuring a firewall policy. Session table fields and diagnose sys session list: Using a session table (sample proto_state=11). iprope: FortiOS CLI Reference — diagnose firewall iprope lookup. VDOM scope: VDOM overview (management VDOM defaults to root).

5. Runbook Side A → B → C

Goal on FGT-LAB: Finance host 10.10.20.45 reaches 198.51.100.80:443 through policy 14 Finance-SaaS, SNAT pool 203.0.113.20, in VDOM root on the HA primary. Prove it with session fields, not a new Accept.

Side A — identity of the factory

  1. State VDOM and HA before any object edit

    GUI: top-bar VDOM selector (when VDOMs are on) and System → HA. CLI: get system status then get system ha status. Lab expected: NAT mode, HA A-P, this unit master / Primary FGT-LAB, current virtual domain root. Source: VDOM overview + HA status.

  2. Map the ingress interface into a zone

    Network → Interfaces and Network → Interfaces → Zone. Official: a zone groups one or more interfaces so policies can match the group. Lab: port2 in LAN, port1 in WAN. Policies match zones unless you designed interface-to-interface on purpose.

  3. Address and service objects

    Policy & Objects → Addresses / Services. Lab: source 10.10.20.0/24, destination 198.51.100.80, service HTTPS. No live customer FQDN.

Side B — policy lookup, then NAT

  1. Edit the existing accept — do not clone first

    Policy & Objects → Firewall Policy → policy 14 Finance-SaaS. Incoming LAN, outgoing WAN, NAT on, log enabled, schedule always. Source: Configuring a firewall policy.

  2. Run Policy Lookup before you save a second rule

    Same menu: Policy Lookup. Fill source IP, dest IP, dest port, protocol, source interface. Lab must predict policy 14. CLI equivalent: diagnose firewall iprope lookup 10.10.20.45 51990 198.51.100.80 443 tcp.

  3. Bind SNAT you can quote

    Either NAT enable on the policy or Policy & Objects → Central SNAT + IP Pools. Lab reply tuple must show 203.0.113.20:18422. If the pool is exhausted or the ISP does not own that IP, proto_state stays 01.

https://fgt-lab.example.com/#/policy/firewall/lookup
Training mock · not live

Policy & Objects → Firewall Policy → Policy Lookup

Policy Lookup

IPv4IPv6
LAN (port2)
TCP
10.10.20.45
51990
198.51.100.80
443
Matched policy 14 Finance-SaaS · incoming LAN · outgoing WAN · action ACCEPT · NAT enable. This is predicted — not the live session.

FortiOS Administration Guide · Configuring a firewall policy + CLI Reference · diagnose firewall iprope lookup. Next click: retest the same 5-tuple, then diagnose sys session list. Do not add policy 15 from this screen.

https://fgt-lab.example.com/#/policy/firewall/edit/14
Training mock · not live

Policy & Objects → Firewall Policy → Edit Policy 14

Edit Firewall Policy

PolicySecurity ProfilesLogging
Finance-SaaS
14
LAN (port2)
WAN (port1)
LAN-Finance 10.10.20.0/24
SaaS-VIP 198.51.100.80
HTTPS
ACCEPT · NAT enable · IP pool WAN-SNAT 203.0.113.20
Log Allowed Traffic — All Sessions

VDOM context: root. Saving this does not rewrite a session that already exists.

FortiOS Administration Guide · Configuring a firewall policy. After OK: one new first packet, then session list. Existing rows keep the old stamps until timeout or a filtered clear.

Side C — prove, do not hope

  1. Predicted match

    iprope / Policy Lookup must print matched policy: id=14 name=Finance-SaaS, srcintf=port2 dstintf=port1, nat=1.

  2. Live session

    Filter source, then list. Success is proto_state=11, same policy_id=14, SNAT hook present, vd is this VDOM. Fail is 01 with a long duration — first packet installed, reply never arrived.

  3. User transaction, then stop debug

    A real HTTPS GET. If you used debug flow, filter the addr, capture the tuple, then diagnose debug disable and diagnose debug reset. Green HA is not a recovered login.

6. Runtime CLI on FGT-LAB

Commands below match official FortiOS diagnose syntax. Output is dummy lab (RFC 5737). Confirm the field names on your production release before you type on a real box. Never leave debug flow on.

Proof · the desk that closes the ticket
Teaches: verification is a live session row with proto_state and policy_id, not a new Accept
Notice: success is fields you can quote — vd, policy_id, proto_state, SNAT hook — not “we added an allow.”
FGT-LAB # get system status
Version: FortiGate-VM64 v7.4.5,build2702,240822 (GA.M)
Serial-Number: FGTVM0LAB000001
Hostname: FGT-LAB
Operation Mode: NAT
Current virtual domain: root
Current HA mode: a-p, master
Cluster uptime: 14 days 2:11:08

What it proves: build, NAT mode, this VDOM, this unit is HA master. What it does not prove: a healthy Finance session.

FGT-LAB (root) # diagnose sys session filter clear FGT-LAB (root) # diagnose sys session filter src 10.10.20.45 FGT-LAB (root) # diagnose sys session filter dst 198.51.100.80 FGT-LAB (root) # diagnose sys session filter dport 443 FGT-LAB (root) # diagnose sys session filter proto 6 FGT-LAB (root) # diagnose sys session filter
session filter:
        vd: 0
        proto: 6
        src: 10.10.20.45-10.10.20.45
        dst: 198.51.100.80-198.51.100.80
        dport: 443-443
filter applied.

Filter is armed. It does not list sessions. Forgetting this step — or forgetting you are still in root — is a junior FortiOS miss. Official: diagnose sys session filter then list (Using a session table).

FGT-LAB (root) # diagnose sys session list
session info: proto=6 proto_state=01 duration=48 expire=3552 timeout=3600
  orig-tuple: 10.10.20.45:51990 -> 198.51.100.80:443
  reply-tuple: 198.51.100.80:443 -> 203.0.113.20:18422
  hook=post dir=org act=snat 10.10.20.45:51990->203.0.113.20:18422
  hook=pre dir=reply act=dnat 198.51.100.80:443->10.10.20.45:51990
  policy_id=14 pol_idx=0 auth=no
  npu_state=00000000
  total: 1 session listed
Green success = these fields
FGT-LAB (root) # diagnose firewall iprope lookup 10.10.20.45 51990 198.51.100.80 443 tcp
lookup: proto=6 10.10.20.45:51990 -> 198.51.100.80:443
matched policy: id=14 name=Finance-SaaS
  srcintf=port2 dstintf=port1
  action=accept nat=1 log=enable
  schedule=always service=HTTPS

Official CLI: “Lookup firewall policy that matches provided criteria.” Prediction is not the live session. Compare policy_id in the list output. If they disagree, the first packet already installed an older row — isolate why. Do not add a third policy.

FGT-LAB (root) # diagnose debug flow filter addr 10.10.20.45 FGT-LAB (root) # diagnose debug flow show function-name enable FGT-LAB (root) # diagnose debug console timestamp enable FGT-LAB (root) # diagnose debug flow trace start 10 FGT-LAB (root) # diagnose debug enable
id=20085 trace_id=17 func=print_pkt_detail msg='vd-root:0 received a packet(proto=6, 10.10.20.45:51990->198.51.100.80:443) from port2.'
id=20085 trace_id=17 func=fw_forward_handler msg='Check iprope policy, result=ok policy 14'
id=20085 trace_id=17 func=fw_forward_handler msg='SNAT 10.10.20.45->203.0.113.20:18422'
id=20085 trace_id=17 func=ip_session_install msg='session installed'

Flow shows receive, policy 14, SNAT, session install — that is the first-packet path. It does not show the server SYN-ACK. That is why proto_state stays 01. Filter tightly. Then:

Turn it off
diagnose debug disable
diagnose debug reset
diagnose debug flow filter clear

7. Traps + proof checklist

SymptomLikely causeFirst checkUnsafe change
Accept + connecting spinnerFirst packet installed; proto_state=01Session list + return path + SNAT poolAdd policy 15
Policy edited, traffic still oldExisting session riding old stampsCompare iprope vs policy_id on the rowDelete the whole session table
Empty session table after failoverStandby unit or wrong VDOMget system status + HA + VDOM selectorClone all policies in root
iprope says 14, session says otherPredicted vs live cacheFiltered clear of the lab tuple after change-controlA third Accept “to be safe”
debug flow left onCPU / log floodFilter addr + disable + resetUnfiltered flow on production
Session in CustA, you listed rootVDOM-scoped tableTop-bar selector or config vdom / edit CustAInvent a missing policy in root
Isolate vs change-control

Isolate with read-only diagnose: status, HA, VDOM, session filter/list, iprope, routing table. Change-control is a new policy, NAT pool, or a filtered session clear — one owner, one reversible change, same 5-tuple retest. diagnose debug flow is isolate only when filtered and turned off.

Proof checklist

Interview close: “FortiGate is a session factory. I state VDOM and HA role, then I ask whether a session already exists. First packet does policy lookup and installs the row. Later packets ride it. I quote proto_state and iprope. I do not add Accept rules to fix a one-way handshake.”

Knowledge check

Six judgment items. Map each to a promise bullet or trap. Check answers, then Reset if you miss.

Q1

Policy 14 is Accept. The browser stays on connecting. Dummy session shows proto_state=01 and SNAT to 203.0.113.20. First move?

Correct: b. Quick answer / Flow 2. Accept already happened; the factory printed a row. 01 is an unfinished handshake. Re-read Runtime CLI.
Q2

What is the real diamond on a FortiGate packet?

Correct: a. Mental model + Flow 2. Policy list is not re-walked on every packet. Re-read First packet vs existing session.
Q3

After a power event the cluster widget is green and the session table looks empty. What do you state first?

Correct: b. Traps table. Empty table on standby or in the wrong VDOM is self-inflicted. Re-read Side A.
Q4

You changed the SNAT pool on policy 14. iprope now shows the new pool. The live HTTPS session still SNATs to the old address. Why?

Correct: c. Flow 2 fact 2 / How to choose. Later packets ride the row. Re-read the first-packet trap.
Q5

Policy Lookup / diagnose firewall iprope lookup prints policy 14 accept+NAT. What does that prove?

Correct: d. Runtime CLI. iprope is FortiGate’s policy-test equivalent, not the session. Re-read Side C.
Q6

How do you use diagnose debug flow on a real box?

Correct: a. Lab dummy flow stops at session install (first packet). Isolate vs change-control. Re-read traps.

Sources

Related: FortiGate evidence desk · FortiGate policy lookup + NAT · FortiGate VDOMs · FortiGate interview · FortiGate hub