A real-life situation
At Elmside Logistics, a fictional company, users say a new supplier portal "doesn't work" while everything else does. One engineer adds a route, another adds a policy, a third disables a security profile. None of it helps, because nobody knows where the FortiGate is dropping the traffic. An engineer who understands packet flow asks one question at a time: did the packet arrive, which session did it match, where was it routed, which policy matched, was it translated, and did the reply come back? Each answer comes from one command. This lesson explains the path those questions follow.
The examples use the course lab:
- 1. Outbound: PC1 (10.0.1.10) opens a web page on 198.51.100.80. Policy 3 allows it with source NAT to 203.0.113.2.
- 2. Inbound: A visitor reaches WEB1 (10.0.2.10) through the virtual IP 203.0.113.20.
Why packet flow matters
A firewall makes several separate decisions for each new connection, in a fixed order. Each decision depends on the ones before it: the policy lookup needs the outgoing interface, which comes from routing, which needs the real destination, which depends on whether destination NAT changed it. If you know the order, a symptom points straight at one step. If you don't, every change is a guess, and guesses on a production firewall cause outages.
Two paths: the first packet and every packet after it
A FortiGate is a stateful firewall. It doesn't make a full decision for every packet. It makes one for the first packet of a connection, records the result as a session, and then matches later packets in both directions against that session.
| First packet (no session yet) | Later packets (session exists) | |
|---|---|---|
| Where | CPU, the "slow path" | CPU fast path, or a network processor if the session was offloaded |
| Lookups | Destination NAT, route, reverse path, policy | None: the session already holds the answers |
| Result | A new session, or a drop | Forwarded and translated according to the session |
| Security profiles | Start inspecting | Keep inspecting the session's data, in both directions |
This is why a FortiGate needs no policy for return traffic, why most packets cost far less work than the first one, and why a policy change mostly matters to new connections.
The first packet, step by step
Here is the path for the common case: a FortiGate in NAT mode, policies with their own NAT setting (policy NAT, the default), and flow-based inspection. The example is PC1's first packet to a web server: a TCP SYN from 10.0.1.10:51544 to 198.51.100.80:443, arriving on port2.
1. Ingress
The SYN arrives on port2. Before anything else, the FortiGate checks that the IP header is well-formed and, if a DoS policy is configured on the interface, counts the packet against its thresholds (floods are stopped here, before they cost policy lookups). If the packet were ESP arriving on an IPsec tunnel, it would be decrypted at this point, and the inner packet would continue as if it had arrived on the tunnel interface.
2. Session lookup
The FortiGate looks for a session with the same protocol, addresses and ports, in either direction. For this SYN there is none, so it continues on the slow path. For every later packet of this connection there will be one, and steps 3 to 5 are skipped.
3. Destination NAT
If the destination matched the external address (and port) of a virtual IP, it would be rewritten here to the internal server's address. Doing this before routing and policy is what makes publishing servers work: the route lookup finds the server's real interface, and the policy is written towards it. The SYN's destination, 198.51.100.80, matches no VIP, so nothing changes.
4. Routing and the reverse path check
The FortiGate looks up 198.51.100.80 in its routing table. The best match is the default route via 203.0.113.1 on port1, so port1 becomes the outgoing interface. That matters for the next step: policies are selected by incoming and outgoing interface, so routing decides which policies are even considered. (A policy route or SD-WAN rule, if configured, is checked before the routing table.)
At the same time the FortiGate runs the reverse path forwarding (RPF) check on the source: is 10.0.1.10 reachable back through port2? It is, through the connected route. A source that isn't reachable through its arrival interface is likely spoofed, and is dropped here, before any policy. By default the check is loose: any route back through that interface, including a default route, passes.
5. Firewall policy
Policies for port2 → port1 are checked from the top of the list. The first policy whose incoming and outgoing interfaces, source, destination, service, schedule and (if set) users all match decides. On FGT1 that is policy 3, LAN-to-Internet. If none matched, the packet would reach the implicit deny (policy 0) and be dropped; no session would be created, so each retry would repeat the whole lookup.
An accept creates the session. From now on the session carries the decision: the policy ID, the interfaces, the NAT to apply, the security profiles, and a timer that is refreshed by traffic. If the policy requires a user group and the source isn't authenticated yet, the FortiGate handles authentication at this point (for example by redirecting a web request to the captive portal).
6. Security inspection
If the policy has security profiles, they now inspect the session. In flow-based mode, the IPS engine examines packets as they pass, in a single pass, without holding whole files; a match can block or reset the session. In proxy-based mode, content is buffered and reassembled (for example a whole file) before it is scanned and released. This SYN carries no data, so there is nothing to inspect yet; the data that follows is.
7. Source NAT
Policy 3 has NAT enabled with no IP pool, so the source is rewritten to port1's address, 203.0.113.2, keeping the source port if it is free. The session records the mapping, so the replies can be translated back without any lookup. Source NAT happens late on purpose: the policy was matched on the original source, so you write policies with your internal addresses.
8. Egress
The packet leaves port1 towards 203.0.113.1. If the outgoing interface were an IPsec tunnel, it would be encrypted first; if a traffic shaper applied, it would be queued accordingly.
Inside the session table
The session is the heart of stateful forwarding, and you can read it. Filter first, so you see only what you are investigating:
diagnose sys session filter clear
diagnose sys session filter src 10.0.1.10
diagnose sys session filter dport 443
diagnose sys session listFilters combine. Always clear old ones first; an old filter left in place is a classic reason for 'no sessions'.
FGT1 # diagnose sys session list session info: proto=6 proto_state=01 duration=41 expire=3559 timeout=3600 flags=00000000 ... ... state=log may_dirty f00 statistic(bytes/packets/allow_err): org=2103/12/1 reply=48211/39/1 tuples=2 ... orgin->sink: org pre->post, reply pre->post dev=5->3/3->5 gwy=203.0.113.1/10.0.1.10 hook=post dir=org act=snat 10.0.1.10:51544->198.51.100.80:443(203.0.113.2:51544) hook=pre dir=reply act=dnat 198.51.100.80:443->203.0.113.2:51544(10.0.1.10:51544) ... misc=0 policy_id=3 ... npu_state=0x... npu info: flag=0x00/0x00, offload=0/0, ips_offload=0/0, ... total session 1
proto=6 is TCP and proto_state=01 means established; expire counts down and is reset by traffic; dev=5->3/3->5 are the incoming and outgoing interface indexes for each direction; gwy is the next hop each way; the act=snat and act=dnat lines show the translation in brackets for the original and reply directions; policy_id=3 names the policy. The npu fields say whether the session is offloaded; zeros mean it isn't. The exact field names vary between releases and models.Later packets and return traffic
When the web server answers, the SYN-ACK arrives on port1 addressed to 203.0.113.2:51544. The session lookup finds the session in its reply direction, and everything follows from the entry: the destination is translated back to 10.0.1.10:51544 (the reverse of the source NAT), the packet leaves port2, and the session's security profiles inspect the reply data. There is no routing lookup for the decision, no VIP check, and no policy lookup. That is why replies need no policy of their own, and why a reply that arrives on the wrong interface is a problem (see asymmetric routing below).
Hardware offloading
Most hardware FortiGates have network processors (NPs). Once a session is established on the CPU, the FortiGate can hand it to the NP: the NP keeps a copy of the session and forwards matching packets itself, translating addresses on the way, at line rate and without CPU work. Content processors (CPs) separately accelerate encryption and some inspection work for the CPU.
- The first packets of every session are always handled by the CPU; only established sessions are offloaded.
- Offload needs both interfaces to be served by the NP, and a session that doesn't need CPU processing. Proxy-based inspection keeps a session on the CPU; how much flow-based inspection can be offloaded depends on the NP generation.
- FortiGate VMs have no NP: their "fast path" is in software.
- Offloaded packets are invisible to the CPU, so the sniffer and debug flow stop seeing them after the start of the session. This surprises many engineers during troubleshooting.
Which sessions can be offloaded, and with which features, depends on the NP generation of your model and on the FortiOS release. The concepts in this lesson are the same in FortiOS 7.4 and 7.6; the exact capabilities are in the hardware acceleration guide for your release.
Explore it yourself
Pick a scenario and step through it. Watch which stages each packet uses, which it skips, and where its addresses change.
Educational model · simplified FortiOS 7.6 behaviour for the chosen scenario (NAT mode, policy NAT, flow-based inspection). Not a FortiGate.
Choose a packet
- Ingress· processed
- Session lookup
- Destination NAT
- Routing
- Policy
- Inspection
- Source NAT
- Egress
Step 1 of 8 · Ingress
The SYN arrives on port2
The interface receives the frame. Basic checks run on the IP header (a malformed header is dropped here), and a DoS policy would be checked if one were configured on port2.
Packet now: 10.0.1.10:51544 → 198.51.100.80:443 (TCP SYN)
See it on a FortiGate: diagnose sniffer packet port2 'host 10.0.1.10 and port 443' 4
When the order is different
The sequence above is not universal. These configurations change it:
| Configuration | What changes |
|---|---|
| Central NAT | VIPs are applied automatically and policies use the real server address. Source NAT comes from the central SNAT table after the policy decision, not from the policy. |
| Policy-based NGFW mode | Security policies can match applications, so the final decision may be made after the first packets, once the application is identified; SSL inspection and authentication are handled by a separate policy first. |
| Transparent mode | The FortiGate bridges: frames are forwarded by MAC address within a forwarding domain, with no IP routing step, but policies still apply. |
| IPsec | Inbound ESP is decrypted at ingress and the inner packet is processed as arriving on the tunnel interface; outbound traffic is encrypted at egress. |
| Traffic to the FortiGate itself | Management, ping to its interfaces, VPN negotiation and routing protocols are local traffic, checked against administrative access and local-in policies, not forward policies. |
| Policy routes and SD-WAN | They are consulted before the routing table at step 4, and can choose a different outgoing interface. |
Diagnostic commands for each step
| Question | Command |
|---|---|
| Did the packet arrive, and did it leave? | diagnose sniffer packet any 'host 10.0.1.10' 4 |
| Which route will it use? | get router info routing-table details 198.51.100.80 |
| Which policy should it match? | Policy & Objects › Firewall Policy › Policy Lookup |
| What did the FortiGate decide? | diagnose debug flow … (below) |
| Is there a session, translated how, offloaded? | diagnose sys session list (with filters) |
diagnose debug reset
diagnose debug flow filter addr 198.51.100.80
diagnose debug flow show function-name enable
diagnose debug console timestamp enable
diagnose debug flow trace start 20
diagnose debug enableReproduce the problem with a new connection, read the trace, then stop it with diagnose debug disable.
⚠ Use debug safely on production FortiGates
Always set a filter and a packet count before diagnose debug enable; an unfiltered trace on a busy firewall loads the CPU and floods the console. Finish with diagnose debug disable and diagnose debug reset. The sniffer is also CPU work: filter it tightly and stop it with Ctrl+C.
FGT1 # (debug flow for PC1's first packet, trimmed) msg="vd-root:0 received a packet(proto=6, 10.0.1.10:51544->198.51.100.80:443) from port2. flag [S], ..." msg="allocate a new session-0003a1f2" msg="find a route: flag=04000000 gw-203.0.113.1 via port1" msg="Allowed by Policy-3: SNAT" msg="SNAT 10.0.1.10->203.0.113.2:51544"
Common packet-flow problems
"no session matched": asymmetric routing
A reply (or a later packet) arrives on an interface the session doesn't expect, or arrives without the FortiGate ever seeing the first packet, because traffic leaves through one firewall or path and comes back through another. The FortiGate finds no session for a packet that isn't a new connection, and drops it. Debug flow shows no session matched.
Show the fix
Fix the routing so both directions pass through the same FortiGate (and the same interfaces): check the routes on neighbouring routers and the server's default gateway. FortiOS has a setting that permits asymmetric traffic (set asymroute enable under config system settings), but it weakens stateful protection and is a last resort, not a fix.
"reverse path check fail, drop"
A new connection arrives from a source the FortiGate can't route back through the arrival interface, typically a new subnet behind an internal router that nobody added a route for.
Show the fix
Add the missing route for that subnet via the correct interface and next hop. Don't weaken the check: the drop is correct, and the route is what's missing.
"Denied by forward policy check (policy 0)"
No policy matched, often because routing chose a different outgoing interface than the policy expects.
Show the fix
Check the route first (it decides the outgoing interface), then Policy Lookup for the exact connection, then policy order. Add or correct the policy only after you know which interface pair is being used.
The sniffer shows the start of a connection, then nothing
The session was offloaded to the NP after the first packets, so later packets bypass the CPU where the sniffer and debug flow run. The traffic may be working perfectly.
Show how to see the whole session
Check the session list: offload fields show it was offloaded. If you must capture the whole conversation, disable offload on a test policy that matches only your test traffic:
config firewall policy
edit 30
set auto-asic-offload disable
next
endRe-enable it (set auto-asic-offload enable) when finished.
⚠ This moves traffic onto the CPU
Every session matching that policy is now processed by the CPU. On a busy policy this can raise CPU load sharply. Use a narrow test policy, in a maintenance window, and switch offload back on afterwards.
Best practices
- Troubleshoot in packet-flow order: arrival, session, route, policy, NAT, reply. Change one thing at a time.
- Test with a new connection after every change; existing sessions may still carry the old decision.
- Filter every diagnostic, limit debug traces, and always disable debug output when finished.
- Make sure servers and neighbouring routers send replies back through the same FortiGate.
- Know which of your sessions are offloaded before relying on sniffer captures.
🛠 Practical exercise: trace one connection end to end
On a FortiGate VM in a lab, start a sniffer on the LAN and WAN interfaces filtered for one test host, run debug flow for the same host with a trace count of 20, and open one HTTPS site. Match each debug line to a stage in this lesson, then find the session in diagnose sys session list and identify the translated address and the policy ID. Finally, try a connection your policies don't allow and compare the output. Write down which stage the second connection stopped at, and why.
Key takeaways
- The first packet takes the slow path: session lookup, destination NAT, routing and RPF, policy, inspection, source NAT, egress.
- The resulting session carries every decision; later packets and replies just match it.
- Destination NAT comes before routing and policy; source NAT comes after the policy.
- Established sessions can be offloaded to an NP, which hides them from the sniffer and debug flow.
- Central NAT, NGFW policy mode, transparent mode, IPsec and local traffic change parts of the order.
Check yourself
An Internet visitor connects to a VIP (203.0.113.20 → WEB1 10.0.2.10). Which address does the routing lookup use?
PC1's TCP session to a web server is established. Which lookups does the server's next reply packet go through?
Debug flow for a new connection shows a route found via port1 and then "Denied by forward policy check (policy 0)". What do you know?
Replies from a server arrive on a different FortiGate interface from the one the requests left, and are dropped with "no session matched". What is the real fix?
A sniffer shows the first few packets of a large download and then nothing, while the download completes normally. Most likely?
Next in the FortiGate Administration course, these steps are applied to policies in How firewall policies work and diagnosed in Which policy did it hit?.