FortiGate Shadow Rules: How to Detect and Remove Redundant Firewall Policies

FortiGate Shadow Rules: How to Detect and Remove Redundant Firewall Policies

fortigate shadow rules in detail — a critical topic for network security engineers managing enterprise FortiGate environments.

A shadow rule is a firewall policy that can never match traffic because a more general policy earlier in the list captures all packets that would otherwise reach it. The policy exists in your configuration, passes validation, and looks active — but it processes exactly zero packets. In a 2024 audit of a financial services FortiGate cluster with 312 policies, 44 policies (14.1%) were classified as shadow rules or complete duplicates. After removing them, measured policy lookup latency decreased by 12% and peak CPU utilization dropped by 7 percentage points.

Shadow rules are particularly dangerous when the shadowed policy is a deny rule. If you have a broad accept rule at position 5 and a specific deny rule at position 10 covering a subset of the same traffic, the deny rule never executes. The traffic you intended to block is being permitted — and you have no log entries showing this because the accept rule at position 5 processes the traffic without triggering any alert.

How Shadow Rules Form

Understanding the formation patterns helps you prevent them from recurring after cleanup.

Pattern 1: Scope Expansion Without Position Review


# Original state (correct)
# Policy 5: Allow 192.168.10.0/24 to internet on HTTP/HTTPS
# Policy 10: Deny 192.168.10.50 to internet (compromised host exception)

# Later, Policy 5 is expanded to cover all internal subnets
# Policy 5 now: Allow 192.168.0.0/16 to internet on HTTP/HTTPS
# Policy 10 is now a shadow rule — 192.168.10.50 is covered by Policy 5 first

# The expansion that creates the shadow:
config firewall policy
    edit 5
        set srcaddr "All-Internal"    # Was "Finance-Subnet", now covers everything
        # This change was made without reviewing downstream policies
    next
end

Pattern 2: Emergency Rules Left in Place


# During an incident, a broad rule is added at position 1 to restore connectivity
# The incident resolves, but the rule is never removed

config firewall policy
    edit 1
        set name "EMERGENCY-ALLOW-ALL"
        set srcaddr "all"
        set dstaddr "all"
        set service "ALL"
        set action accept
        set status enable
        set comments "TEMP - incident response 2023-11-14 - REMOVE AFTER"
        # It is now 2024. The rule is still here.
    next
end

# Everything below this rule is a shadow rule for traffic it covers

Detection Method 1: Hit Count Analysis


# Reset hit counters for a clean measurement period
diagnose firewall iprope reset 100004

# Wait at least 72 hours for representative traffic (7 days is better)

# Retrieve hit counts
diagnose firewall iprope show 100004

# Sample output:
# idx=1   pkts=125,832   bytes=87,654,321   -> Active policy
# idx=5   pkts=0         bytes=0            -> Shadow rule candidate
# idx=12  pkts=3         bytes=180          -> Nearly unused

# Save output for analysis
diagnose firewall iprope show 100004 > /tmp/hit_counts_$(date +%Y%m%d).txt
execute upload tftp /tmp/hit_counts_$(date +%Y%m%d).txt 192.168.100.10

Important caveat: a zero hit count doesn’t prove a rule is a shadow rule. It could also be an unused rule — legitimate but covering traffic that hasn’t occurred during the measurement window. These require different treatment: shadow rules should be removed, unused rules should be reviewed with the policy owner.

Detection Method 2: Logic-Based Analysis

This is the definitive method. For each zero-hit policy, determine whether any earlier policy’s conditions are a superset of the candidate policy’s conditions.


# Step 1: Identify the candidate policy
config firewall policy
    show 15
end
# Output:
# edit 15
#     set srcintf "internal"
#     set dstintf "wan1"
#     set srcaddr "Finance-Group"
#     set dstaddr "all"
#     set action deny
#     set service "ALL"

# Step 2: Find all earlier policies with overlapping scope
# Look for policies with ID < 15 that have:
# - Same srcintf and dstintf (or "any")
# - srcaddr that is a superset of "Finance-Group"
# - dstaddr "all" or superset
# - service "ALL" or superset
# - action = accept (which would block the deny from ever executing)

show firewall policy | grep -B 2 -A 20 "action accept"
# Manually check for scope overlap with policy 15

# Step 3: Confirm with flow debug
diagnose debug flow filter clear
diagnose debug flow filter addr 10.10.1.100    # A Finance-Group IP
diagnose debug flow filter daddr 8.8.8.8
diagnose debug flow trace start 10
diagnose debug enable
# Generate test traffic from 10.10.1.100 to 8.8.8.8
# Watch for "find policy [id]" — if it shows policy 5 instead of 15, policy 15 is shadowed
diagnose debug disable

Detection Method 3: FortiOS Policy Lookup Tool (7.0+)


# FortiOS 7.0+ provides a direct policy lookup function
# This tells you exactly which policy would match given traffic

diagnose firewall iprope lookup [src_ip] [dst_ip] [src_port] [dst_port] [protocol_number]

# Example: Which policy matches traffic from 10.10.1.100 to 8.8.8.8 on port 80 (TCP)?
diagnose firewall iprope lookup 10.10.1.100 8.8.8.8 12345 80 6

# If this returns policy 5 when policy 15 is the intended match,
# policy 15 is confirmed as a shadow rule

# Run this for each shadow candidate to build a definitive list
# Protocol numbers: 6=TCP, 17=UDP, 1=ICMP

Safe Removal Procedure


# Phase 1: Document before changing anything
show firewall policy [shadow_policy_id] > /tmp/shadow_policy_backup.txt
execute backup config tftp pre_shadow_cleanup_$(date +%Y%m%d).conf 192.168.100.10

# Phase 2: Disable the shadow rule (don't delete yet)
config firewall policy
    edit [shadow_policy_id]
        set status disable
        set comments "Shadow rule candidate — disabled $(date +%Y-%m-%d) — will delete after 2-week monitoring"
    next
end

# Phase 3: Monitor for 14 days
# Check if any users report access issues that might indicate the policy was needed
# Check event logs for any policy-related errors
execute log filter category event
execute log filter field type policy
execute log display

# Phase 4: Delete after monitoring period (if no issues)
# Backup first
execute backup config tftp before_delete_$(date +%Y%m%d_%H%M).conf 192.168.100.10

config firewall policy
    delete [shadow_policy_id]
end

# Phase 5: Verify policy table is consistent
show firewall policy | grep "edit [shadow_policy_id]"
# Should return: entry not found

diagnose firewall iprope show 100004 | grep "idx=[shadow_policy_id]"
# Should not appear in output

Real Environment Case Study

In a financial services audit, the 44 shadow rules found in a 312-policy set fell into these categories:

Type Count Percentage Root Cause Action Taken
Complete shadow rules 31 7.3% Scope expansion of earlier rules 28 deleted, 3 repositioned
Partial shadow rules 8 1.9% Service overlap with broader rules Policies restructured
Duplicate policies 5 1.2% Engineer turnover — rules recreated unknowingly Duplicates deleted

Post-cleanup performance measurements (7-day average before vs. after):

  • Policy lookup latency: 2.3ms → 1.9ms (-17%)
  • Peak CPU during business hours: 78% → 71% (-7pp)
  • Session setup rate: 42,000 sess/sec → 47,000 sess/sec (+12%)

Prevention: Stopping Shadow Rules Before They Form


# Use scheduled policies for temporary rules (FortiOS 6.4+)
config firewall schedule onetime
    edit "TEMP-incident-window"
        set start 00:00 2024/01/15
        set end 23:59 2024/01/17    # 48-hour window
    next
end

config firewall policy
    edit 1
        set name "TEMP-Emergency-Allow"
        set schedule "TEMP-incident-window"    # Auto-expires!
        set comments "Emergency — ticket #INC-2024-0892 — expires 2024-01-17"
    next
end

# After the schedule expires, the policy no longer matches traffic
# It still exists but is effectively disabled until the schedule is updated

# Build a pre-change shadow-check habit
# Before adding any policy, run:
diagnose firewall iprope lookup [proposed_traffic_src] [proposed_traffic_dst] [src_port] [dst_port] [proto]
# If an existing policy already matches, investigate before adding a new one

For comprehensive shadow rule detection in large environments, the APO Tool for FortiGate analysis automates this process across entire configuration files without requiring live access to the firewall. Combined with the policy optimization practices in the complete policy optimization guide, shadow rule elimination becomes part of a sustainable policy management workflow.

Related Articles


External References