Tag: web application firewall

  • Quick tips: See HTTP headers in FortiGate forwarded traffic, view decrypted traffic on a FortiGate without an external device

    If, dear reader, you ever wanted to see HTTP headers in forwarded traffic through a FortiGate or how to view decrypted traffic without having to mirror it to an external device, have I got some tips for you.

    Models and versions:

    • FortiGate 70G on 7.6.7
    • Ubuntu server on 24.04

    See HTTP headers in FortiGate forwarded traffic

    For troubleshooting purposes, it can help to see what HTTP headers are being sent to a host. In my case, I needed to find out what an incoming HTTP POST request looked like to fix my backend configuration.

    In order to facilitate this, you need a proxy-based firewall policy that uses some security profile other than the SSL/SSH profile (just the SSL/SSH profile alone means no inspection is performed), because we need the traffic to hit the wad process, and if your traffic is HTTPS, you need deep inspection as well. The type of security profile doesn’t actually matter. I have tested anti-virus, web filter, DNS filter, application control, IPS, file filter, and web application firewall, and all of them worked. For my security profile, I am using a Web Application Firewall (WAF) profile because it requires the least amount of CLI configuration and a deep inspection SSL/SSH profile.

    config waf profile
        edit "WAF_HTTP-HEADERS"
        next
    end

    With the WAF profile created and your deep inspection set up, if necessary, you can create a proxy-based policy with both profiles configured. Remember that you need “Web Application Firewall” enabled in Feature Visibility to see the GUI option.

    FortiGate HTTP headers policy
    Only important parts are shown!
    
    config firewall policy
        edit 0
            set name "HTTP-HEADERS-POLICY"
            set utm-status enable
            set inspection-mode proxy
            set ssl-ssh-profile "custom-deep-inspection"
            set waf-profile "WAF_HTTP-HEADERS"
        next
    end

    Because proxy-based web inspection uses the wad process, we need the debugs for the wad category http enabled and a level of info. Once our traffic happens, the debugs will fill up quickly.

    diagnose wad debug enable category http
    diagnose wad debug enable level info
    diagnose debug enable

    I am using the following curl command to a webhook.site endpoint:

    curl -X POST -H 'Authorization: Bearer TESTTOKEN' -H 'Content-type: application/json' -d '{"name":"Kevin Guenay"}' https://webhook.site/6z7993e7-4679-4003-b4f1-18f72b628fe1

    The full debug of the request is as follows:

    WAD debug output

    [I][p:1760][s:3147]       wad_url_cate_dump_req_ctx         :327   (fetch-done): req/wfp=0/0 cate: cate=255 webf=255 sslexempt=255 url/ip=255/255 don
    e: bal=0,local/user/cache/ftgd=1/1/1/1   matched[url]: block/allow/user=0/0/0 ftgd=0 sub=1 log=0 invalid=0
    [I][p:1760][s:3147]       wad_url_choose_cate               :2504  cate=52 (ftgd) url-cates=[52,]; url=[ # 52,],ip=[ # 0,];  conf sslexempt_rating :[33,31,]
    [I][p:1760][s:3147]       wad_http_srv_selector_static_make :1956  make static server selector.
    [I][p:1760][s:3147]       wad_http_srv_slct_static_set_connectable:1127  static server selector connectable set to 0.
    [I][p:1760][s:3147]       wad_http_srv_slct_static_set_connectable:1127  static server selector connectable set to 0.
    [I][p:1760][s:3147]       wad_http_full_ses_make            :15978 hs(0x7f7e8b91f0) switch to H2.
    [I][p:1760][s:3147]       wad_http_full_ses_make            :16013 make ok session=0x7f8983fa28 server=0x7f814882a8.
    [I][p:1760][s:3147][r:50331677] wad_dump_http_request             :3134  hreq=0x7f7eb63700 Received request from client: 192.168.1.151:53566
    
    POST /6z7993e7-4679-4003-b4f1-18f72b628fe1 HTTP/2.0
    Host: webhook.site
    user-agent: curl/8.5.0
    accept: */*
    content-type: application/json
    content-length: 23
    
    [I][p:1760][s:3147][r:50331677] wad_http_parse_host               :2019  host=[12]webhook.site
    [I][p:1760][s:3147][r:50331677] wad_http_str_canonicalize         :2621  enc=0 path=/6z7993e7-4679-4003-b4f1-18f72b628fe1 len=37 changes=0
    [I][p:1760][s:3147][r:50331677] wad_http_req_detect_special       :14589 captive_portal detected: false, preflight=(null)
    [I][p:1760][s:3147][r:50331677] wad_http_waf_access_control       :1242
    [I][p:1760][s:3147][r:50331677] wad_http_req_proc_waf_body        :1346
    [I][p:1760][s:3147][r:50331677] wad_http_req_proc_waf_body        :1346
    [I][p:1760][s:3147][r:50331677] wad_http_waf_check_req            :1205  WAF sanity check msg=0x7f7eb63700 rid=83
    [I][p:1760][s:3147][r:50331677] wad_http_srv_attach_req           :939   [0x7f7eb63700] Use old server0x7f82fa75d8: :0
    [I][p:1760][s:3147][r:50331677] wad_dump_fwd_http_req             :3148  hreq=0x7f7eb63700 Forward request to server:
    POST /6z7993e7-4679-4003-b4f1-18f72b628fe1 HTTP/2.0
    Host: webhook.site
    user-agent: curl/8.5.0
    accept: */*
    authorization: Bearer TESTTOKEN
    content-type: application/json
    content-length: 23
    
    [I][p:1760][s:3147][r:50331677] wad_dump_http_resp                :3163  hreq=0x7f7eb63700 Received response from server:
    
    HTTP/2.0 200 OK
    server: nginx
    content-type: text/html; charset=UTF-8
    x-request-id: 083ca667-b3dd-4597-89b4-c8ec44a29127
    x-token-id: 6z7993e7-4679-4003-b4f1-18f72b628fe1
    content-security-policy: signup-to-remove-csp-header 'webhook.site/register'; base-uri 'self'; form-action 'none'; script-src 'none'; worker-src 'none'; frame-src 'non
    e';
    cache-control: no-cache, private
    date: Fri, 14 Aug 2026 19:05:21 GMT
    
    [I][p:1760][s:3147][r:50331677] wad_http_fwd_non_cacheable_resp   :2630  resp(0x7f7df439f0) starts processing.
    [I][p:1760][s:3147][r:50331677] wad_http_resp_setup_fwd_resp      :2607  msg(0x7f7df439f0) build fwd resp!
    [I][p:1760][s:3147][r:50331677] wad_dump_fwd_http_resp            :3155  hreq=0x7f7eb63700 Forward response from Server:
    
    HTTP/2.0 200 OK
    server: nginx
    content-type: text/html; charset=UTF-8
    x-request-id: 083ca667-b3dd-4597-89b4-c8ec44a29127
    x-token-id: 6z7993e7-4679-4003-b4f1-18f72b628fe1
    content-security-policy: signup-to-remove-csp-header 'webhook.site/register'; base-uri 'self'; form-action 'none'; script-src 'none'; worker-src 'none'; frame-src 'non
    e';
    cache-control: no-cache, private
    date: Fri, 14 Aug 2026 19:05:21 GMT
    
    [I][p:1760][s:3147][r:50331677] wad_http_req_resp_fwd_done        :3488  req(0x7f7eb63700) resp(0x7f7df439f0/0x7f7df439f0) resp-fwd done!
    [I][p:1760][s:3147][r:50331677] wad_http_req_finished             :1607  req=0x7f7eb63700 cstrm=0x7f7e8b9298 req_done=1 done_close=0
    [I][p:1760][s:3147][r:50331677] __wad_http_req_close              :2071  ret = -1!
    [I][p:1760][s:3147]       wad_http_srv_free                 :5196  HTTP server(0x7f82fa75d8) closed.
    [I][p:1760][s:3147]       wad_http_session_free             :14352 http cache session 0x7f7e8b91f0 req=(nil) close
    [I][p:1760]               __wad_http_session_task_end       :14524 wad_http_cstrm_shutdown:15053: hcs=0x7f7e8b91f0 good=0 state=3 processing=1 state=4 processing=1 fre
    ed

    In the debugs, we care about the section after “Forward request to server:”

    [I][p:1760][s:3147][r:50331677] wad_dump_fwd_http_req             :3148  hreq=0x7f7eb63700 Forward request to server:
    POST /6z7993e7-4679-4003-b4f1-18f72b628fe1 HTTP/2.0
    Host: webhook.site
    user-agent: curl/8.5.0
    accept: */*
    authorization: Bearer TESTTOKEN
    content-type: application/json
    content-length: 23

    We can see the URL path the POST got sent to, the host, and the HTTP headers, including my authorization bearer token. You can do with this information what you need.

    View decrypted traffic on a FortiGate without an external device

    The decrypted traffic mirror feature on a FortiGate is an interesting one because it allows you to mirror decrypted traffic to another interface, which can then be captured and inspected by a device, like a dedicated intrusion detection system (IDS). Normally, decrypted traffic is not capturable on a FortiGate itself; you only gain visibility for the internal processes that work with this kind of traffic (IPS, anti-virus, etc.), but with decrypted traffic mirroring and some ingenuity, you can capture this traffic without an external device.

    Note: The decrypted traffic mirror feature should be used with caution. Handling this type of traffic can be an invasion of privacy, and you need to check your local laws to see if this is allowed in your region if you are handling relevant traffic (health and finance information are the most critical types). You will see a message reminding you that this is something you need to be aware of, both in the GUI and the CLI.

    FortiGate decrypted traffic mirror warning

    Now that you are aware of the problems, let’s get to configuring.

    If you are configuring a policy with an SSL/SSH profile performing deep inspection, you get the “Decrypted traffic mirror” option, and there you can select an existing mirror port or create one inline. I haven’t tested all options, but I think all types of interfaces can be mirror ports, including VLANs and loopbacks. For this post, I am enabling the feature for a VLAN interface. As long as the interface is up, it is valid, so you can create a dummy VLAN interface on a parent port that is up.

    config firewall decrypted-traffic-mirror
        edit "DTM_VLAN"
            set dstmac ff:ff:ff:ff:ff:ff
            set traffic-type ssl ssh
            set traffic-source both
            set interface "VL_TEST"
        next
    end

    Once your policy is configured accordingly, you can capture traffic on it, either in the GUI via Network -> Diagnostics -> Packet capture or on the CLI using diagnose sniffer packet <INTERFACE> ‘<FILTER>’ 6 0 a, e.g. diagnose sniffer packet VL_TEST ‘host 192.168.1.151’ 6 0 a. The 6 is used so the headers and data from packets are captured along with the interface name.

    FortiGate decrypted traffic mirror policy
    Only important parts are shown!
    
    config firewall policy
        edit 0
            set name "DTM-POLICY"
            set utm-status enable
            set inspection-mode proxy
            set ssl-ssh-profile "custom-deep-inspection"
            set waf-profile "WAF_HTTP-HEADERS"
            set decrypted-traffic-mirror "DTM_VLAN"
        next
    end

    Remember that you need at least one security profile in a policy performing any form of inspection that isn’t the SSL/SSH profile; otherwise, no inspection happens. That is the reason why the WAF profile that I created in the other accompanying quick tip is there.

    If you want to view the traffic in Wireshark and you have performed the capture on the GUI, you can download the PCAP immediately, and if you do it on the CLI, you need to capture and convert the output using a Fortinet-provided tool.

    Using my previous curl command, just with the --http1.1 option added, the capture shows the HTTP headers as well as the payload. The --http1.1 option is optional, but without it, not all information may be visible due to possible HTTP/2 compression, which webhook.site uses.

    FortiGate decrypted traffic capture

    Decrypted traffic mirror CLI capture

    2026-08-14 19:31:34.499189 VL_TEST -- 192.168.1.202.46574 -> 178.63.67.153.443: syn 0 
    0x0000   ffff ffff ffff 0009 0f09 0100 0800 4500        ..............E.
    0x0010   0028 0000 0000 4006 c285 c0a8 01ca b23f        .(....@........?
    0x0020   4399 b5ee 01bb 0000 0000 0000 0000 5002        C.............P.
    0x0030   ffff 3fee 0000                                 ..?...
    
    2026-08-14 19:31:34.499199 VL_TEST -- 178.63.67.153.443 -> 192.168.1.202.46574: syn 0 ack 1 
    0x0000   ffff ffff ffff 0009 0f09 0100 0800 4500        ..............E.
    0x0010   0028 0000 0000 4006 c285 b23f 4399 c0a8        .(....@....?C...
    0x0020   01ca 01bb b5ee 0000 0000 0000 0001 5012        ..............P.
    0x0030   ffff 3fdd 0000                                 ..?...
    
    2026-08-14 19:31:34.499202 VL_TEST -- 192.168.1.202.46574 -> 178.63.67.153.443: ack 1 
    0x0000   ffff ffff ffff 0009 0f09 0100 0800 4500        ..............E.
    0x0010   0028 0000 0000 4006 c285 c0a8 01ca b23f        .(....@........?
    0x0020   4399 b5ee 01bb 0000 0001 0000 0001 5010        C.............P.
    0x0030   ffff 3fde 0000                                 ..?...
    
    2026-08-14 19:31:34.499210 VL_TEST -- 192.168.1.151.46574 -> 178.63.67.153.443: syn 0 
    0x0000   ffff ffff ffff 0009 0f09 0100 0800 4500        ..............E.
    0x0010   0028 0000 0000 4006 c2b8 c0a8 0197 b23f        .(....@........?
    0x0020   4399 b5ee 01bb 0000 0000 0000 0000 5002        C.............P.
    0x0030   ffff 4021 0000                                 ..@!..
    
    2026-08-14 19:31:34.499213 VL_TEST -- 178.63.67.153.443 -> 192.168.1.151.46574: syn 0 ack 1 
    0x0000   ffff ffff ffff 0009 0f09 0100 0800 4500        ..............E.
    0x0010   0028 0000 0000 4006 c2b8 b23f 4399 c0a8        .(....@....?C...
    0x0020   0197 01bb b5ee 0000 0000 0000 0001 5012        ..............P.
    0x0030   ffff 4010 0000                                 ..@...
    
    2026-08-14 19:31:34.499215 VL_TEST -- 192.168.1.151.46574 -> 178.63.67.153.443: ack 1 
    0x0000   ffff ffff ffff 0009 0f09 0100 0800 4500        ..............E.
    0x0010   0028 0000 0000 4006 c2b8 c0a8 0197 b23f        .(....@........?
    0x0020   4399 b5ee 01bb 0000 0001 0000 0001 5010        C.............P.
    0x0030   ffff 4011 0000                                 ..@...
    
    2026-08-14 19:31:34.524767 VL_TEST -- 192.168.1.151.46574 -> 178.63.67.153.443: 1 ack 1 
    0x0000   ffff ffff ffff 0009 0f09 0100 0800 4500        ..............E.
    0x0010   0104 0000 0000 4006 c1dc c0a8 0197 b23f        ......@........?
    0x0020   4399 b5ee 01bb 0000 0001 0000 0001 5010        C.............P.
    0x0030   ffff 2917 0000 504f 5354 202f 3366 6139        ..)...POST./6z79
    0x0040   3933 6537 2d34 3637 392d 3430 3033 2d62        93e7-4679-4003-b
    0x0050   3466 312d 3138 6637 3262 3632 3866 6531        4f1-18f72b628fe1
    0x0060   2048 5454 502f 312e 310d 0a48 6f73 743a        .HTTP/1.1..Host:
    0x0070   2077 6562 686f 6f6b 2e73 6974 650d 0a55        .webhook.site..U
    0x0080   7365 722d 4167 656e 743a 2063 7572 6c2f        ser-Agent:.curl/
    0x0090   382e 352e 300d 0a41 6363 6570 743a 202a        8.5.0..Accept:.*
    0x00a0   2f2a 0d0a 4175 7468 6f72 697a 6174 696f        /*..Authorizatio
    0x00b0   6e3a 2042 6561 7265 7220 5445 5354 544f        n:.Bearer.TESTTO
    0x00c0   4b45 4e0d 0a43 6f6e 7465 6e74 2d74 7970        KEN..Content-typ
    0x00d0   653a 2061 7070 6c69 6361 7469 6f6e 2f6a        e:.application/j
    0x00e0   736f 6e0d 0a43 6f6e 7465 6e74 2d4c 656e        son..Content-Len
    0x00f0   6774 683a 2032 330d 0a0d 0a7b 226e 616d        gth:.23....{"nam
    0x0100   6522 3a22 4b65 7669 6e20 4775 656e 6179        e":"Kevin.Guenay
    0x0110   227d                                           "}
    
    2026-08-14 19:31:34.524774 VL_TEST -- 178.63.67.153.443 -> 192.168.1.151.46574: ack 221 
    0x0000   ffff ffff ffff 0009 0f09 0100 0800 4500        ..............E.
    0x0010   0028 0000 0000 4006 c2b8 b23f 4399 c0a8        .(....@....?C...
    0x0020   0197 01bb b5ee 0000 0001 0000 00dd 5010        ..............P.
    0x0030   ffff 3f35 0000                                 ..?5..
    
    2026-08-14 19:31:34.524777 VL_TEST -- 192.168.1.202.46574 -> 178.63.67.153.443: 1 ack 1 
    0x0000   ffff ffff ffff 0009 0f09 0100 0800 4500        ..............E.
    0x0010   0104 0000 0000 4006 c1a9 c0a8 01ca b23f        ......@........?
    0x0020   4399 b5ee 01bb 0000 0001 0000 0001 5010        C.............P.
    0x0030   ffff 28e4 0000 504f 5354 202f 3366 6139        ..(...POST./6z79
    0x0040   3933 6537 2d34 3637 392d 3430 3033 2d62        93e7-4679-4003-b
    0x0050   3466 312d 3138 6637 3262 3632 3866 6531        4f1-18f72b628fe1
    0x0060   2048 5454 502f 312e 310d 0a48 6f73 743a        .HTTP/1.1..Host:
    0x0070   2077 6562 686f 6f6b 2e73 6974 650d 0a55        .webhook.site..U
    0x0080   7365 722d 4167 656e 743a 2063 7572 6c2f        ser-Agent:.curl/
    0x0090   382e 352e 300d 0a41 6363 6570 743a 202a        8.5.0..Accept:.*
    0x00a0   2f2a 0d0a 4175 7468 6f72 697a 6174 696f        /*..Authorizatio
    0x00b0   6e3a 2042 6561 7265 7220 5445 5354 544f        n:.Bearer.TESTTO
    0x00c0   4b45 4e0d 0a43 6f6e 7465 6e74 2d74 7970        KEN..Content-typ
    0x00d0   653a 2061 7070 6c69 6361 7469 6f6e 2f6a        e:.application/j
    0x00e0   736f 6e0d 0a43 6f6e 7465 6e74 2d4c 656e        son..Content-Len
    0x00f0   6774 683a 2032 330d 0a0d 0a7b 226e 616d        gth:.23....{"nam
    0x0100   6522 3a22 4b65 7669 6e20 4775 656e 6179        e":"Kevin.Guenay
    0x0110   227d                                           "}
    
    2026-08-14 19:31:34.524781 VL_TEST -- 178.63.67.153.443 -> 192.168.1.202.46574: ack 221 
    0x0000   ffff ffff ffff 0009 0f09 0100 0800 4500        ..............E.
    0x0010   0028 0000 0000 4006 c285 b23f 4399 c0a8        .(....@....?C...
    0x0020   01ca 01bb b5ee 0000 0001 0000 00dd 5010        ..............P.
    0x0030   ffff 3f02 0000                                 ..?...
    
    2026-08-14 19:31:34.572780 VL_TEST -- 178.63.67.153.443 -> 192.168.1.202.46574: 1 ack 221 
    0x0000   ffff ffff ffff 0009 0f09 0100 0800 4500        ..............E.
    0x0010   028e 0000 0000 4006 c01f b23f 4399 c0a8        ......@....?C...
    0x0020   01ca 01bb b5ee 0000 0001 0000 00dd 5010        ..............P.
    0x0030   ffff 36c3 0000 4854 5450 2f31 2e31 2032        ..6...HTTP/1.1.2
    0x0040   3030 204f 4b0d 0a73 6572 7665 723a 206e        00.OK..server:.n
    0x0050   6769 6e78 0d0a 636f 6e74 656e 742d 7479        ginx..content-ty
    0x0060   7065 3a20 7465 7874 2f68 746d 6c3b 2063        pe:.text/html;.c
    0x0070   6861 7273 6574 3d55 5446 2d38 0d0a 7472        harset=UTF-8..tr
    0x0080   616e 7366 6572 2d65 6e63 6f64 696e 673a        ansfer-encoding:
    0x0090   2063 6875 6e6b 6564 0d0a 782d 7265 7175        .chunked..x-requ
    0x00a0   6573 742d 6964 3a20 3661 3164 6430 6238        est-id:.6a1dd0b8
    0x00b0   2d35 3135 632d 3462 3836 2d61 3362 662d        -515c-4b86-a3bf-
    0x00c0   6336 3138 6630 6334 6161 3961 0d0a 782d        c618f0c4aa9a..x-
    0x00d0   746f 6b65 6e2d 6964 3a20 3366 6139 3933        token-id:.6z7993
    0x00e0   6537 2d34 3637 392d 3430 3033 2d62 3466        e7-4679-4003-b4f
    0x00f0   312d 3138 6637 3262 3632 3866 6531 0d0a        1-18f72b628fe1..
    0x0100   636f 6e74 656e 742d 7365 6375 7269 7479        content-security
    0x0110   2d70 6f6c 6963 793a 2073 6967 6e75 702d        -policy:.signup-
    0x0120   746f 2d72 656d 6f76 652d 6373 702d 6865        to-remove-csp-he
    0x0130   6164 6572 2027 7765 6268 6f6f 6b2e 7369        ader.'webhook.si
    0x0140   7465 2f72 6567 6973 7465 7227 3b20 6261        te/register';.ba
    0x0150   7365 2d75 7269 2027 7365 6c66 273b 2066        se-uri.'self';.f
    0x0160   6f72 6d2d 6163 7469 6f6e 2027 6e6f 6e65        orm-action.'none
    0x0170   273b 2073 6372 6970 742d 7372 6320 276e        ';.script-src.'n
    0x0180   6f6e 6527 3b20 776f 726b 6572 2d73 7263        one';.worker-src
    0x0190   2027 6e6f 6e65 273b 2066 7261 6d65 2d73        .'none';.frame-s
    0x01a0   7263 2027 6e6f 6e65 273b 0d0a 6361 6368        rc.'none';..cach
    0x01b0   652d 636f 6e74 726f 6c3a 206e 6f2d 6361        e-control:.no-ca
    0x01c0   6368 652c 2070 7269 7661 7465 0d0a 6461        che,.private..da
    0x01d0   7465 3a20 4672 692c 2031 3420 4175 6720        te:.Fri,.14.Aug.
    0x01e0   3230 3236 2031 393a 3331 3a33 3420 474d        2026.19:31:34.GM
    0x01f0   540d 0a0d 0a39 430d 0a54 6869 7320 5552        T....9C..This.UR
    0x0200   4c20 6861 7320 6e6f 2064 6566 6175 6c74        L.has.no.default
    0x0210   2063 6f6e 7465 6e74 2063 6f6e 6669 6775        .content.configu
    0x0220   7265 642e 203c 6120 6872 6566 3d22 6874        red..<a.href="ht
    0x0230   7470 733a 2f2f 7765 6268 6f6f 6b2e 7369        tps://webhook.si
    0x0240   7465 2f23 212f 6564 6974 2f33 6661 3939        te/#!/edit/6z799
    0x0250   3365 372d 3436 3739 2d34 3030 332d 6234        3e7-4679-4003-b4
    0x0260   6631 2d31 3866 3732 6236 3238 6665 3122        f1-18f72b628fe1"
    0x0270   3e43 6861 6e67 6520 7265 7370 6f6e 7365        >Change.response
    0x0280   2069 6e20 5765 6268 6f6f 6b2e 7369 7465        .in.Webhook.site
    0x0290   3c2f 613e 2e0d 0a30 0d0a 0d0a                  </a>...0....
    
    2026-08-14 19:31:34.572785 VL_TEST -- 192.168.1.202.46574 -> 178.63.67.153.443: ack 615 
    0x0000   ffff ffff ffff 0009 0f09 0100 0800 4500        ..............E.
    0x0010   0028 0000 0000 4006 c285 c0a8 01ca b23f        .(....@........?
    0x0020   4399 b5ee 01bb 0000 00dd 0000 0267 5010        C............gP.
    0x0030   ffff 3c9c 0000                                 ..<...
    
    2026-08-14 19:31:34.572788 VL_TEST -- 178.63.67.153.443 -> 192.168.1.151.46574: 1 ack 221 
    0x0000   ffff ffff ffff 0009 0f09 0100 0800 4500        ..............E.
    0x0010   028e 0000 0000 4006 c052 b23f 4399 c0a8        ......@..R.?C...
    0x0020   0197 01bb b5ee 0000 0001 0000 00dd 5010        ..............P.
    0x0030   ffff 36f6 0000 4854 5450 2f31 2e31 2032        ..6...HTTP/1.1.2
    0x0040   3030 204f 4b0d 0a73 6572 7665 723a 206e        00.OK..server:.n
    0x0050   6769 6e78 0d0a 636f 6e74 656e 742d 7479        ginx..content-ty
    0x0060   7065 3a20 7465 7874 2f68 746d 6c3b 2063        pe:.text/html;.c
    0x0070   6861 7273 6574 3d55 5446 2d38 0d0a 7472        harset=UTF-8..tr
    0x0080   616e 7366 6572 2d65 6e63 6f64 696e 673a        ansfer-encoding:
    0x0090   2063 6875 6e6b 6564 0d0a 782d 7265 7175        .chunked..x-requ
    0x00a0   6573 742d 6964 3a20 3661 3164 6430 6238        est-id:.6a1dd0b8
    0x00b0   2d35 3135 632d 3462 3836 2d61 3362 662d        -515c-4b86-a3bf-
    0x00c0   6336 3138 6630 6334 6161 3961 0d0a 782d        c618f0c4aa9a..x-
    0x00d0   746f 6b65 6e2d 6964 3a20 3366 6139 3933        token-id:.6z7993
    0x00e0   6537 2d34 3637 392d 3430 3033 2d62 3466        e7-4679-4003-b4f
    0x00f0   312d 3138 6637 3262 3632 3866 6531 0d0a        1-18f72b628fe1..
    0x0100   636f 6e74 656e 742d 7365 6375 7269 7479        content-security
    0x0110   2d70 6f6c 6963 793a 2073 6967 6e75 702d        -policy:.signup-
    0x0120   746f 2d72 656d 6f76 652d 6373 702d 6865        to-remove-csp-he
    0x0130   6164 6572 2027 7765 6268 6f6f 6b2e 7369        ader.'webhook.si
    0x0140   7465 2f72 6567 6973 7465 7227 3b20 6261        te/register';.ba
    0x0150   7365 2d75 7269 2027 7365 6c66 273b 2066        se-uri.'self';.f
    0x0160   6f72 6d2d 6163 7469 6f6e 2027 6e6f 6e65        orm-action.'none
    0x0170   273b 2073 6372 6970 742d 7372 6320 276e        ';.script-src.'n
    0x0180   6f6e 6527 3b20 776f 726b 6572 2d73 7263        one';.worker-src
    0x0190   2027 6e6f 6e65 273b 2066 7261 6d65 2d73        .'none';.frame-s
    0x01a0   7263 2027 6e6f 6e65 273b 0d0a 6361 6368        rc.'none';..cach
    0x01b0   652d 636f 6e74 726f 6c3a 206e 6f2d 6361        e-control:.no-ca
    0x01c0   6368 652c 2070 7269 7661 7465 0d0a 6461        che,.private..da
    0x01d0   7465 3a20 4672 692c 2031 3420 4175 6720        te:.Fri,.14.Aug.
    0x01e0   3230 3236 2031 393a 3331 3a33 3420 474d        2026.19:31:34.GM
    0x01f0   540d 0a0d 0a39 430d 0a54 6869 7320 5552        T....9C..This.UR
    0x0200   4c20 6861 7320 6e6f 2064 6566 6175 6c74        L.has.no.default
    0x0210   2063 6f6e 7465 6e74 2063 6f6e 6669 6775        .content.configu
    0x0220   7265 642e 203c 6120 6872 6566 3d22 6874        red..<a.href="ht
    0x0230   7470 733a 2f2f 7765 6268 6f6f 6b2e 7369        tps://webhook.si
    0x0240   7465 2f23 212f 6564 6974 2f33 6661 3939        te/#!/edit/6z799
    0x0250   3365 372d 3436 3739 2d34 3030 332d 6234        3e7-4679-4003-b4
    0x0260   6631 2d31 3866 3732 6236 3238 6665 3122        f1-18f72b628fe1"
    0x0270   3e43 6861 6e67 6520 7265 7370 6f6e 7365        >Change.response
    0x0280   2069 6e20 5765 6268 6f6f 6b2e 7369 7465        .in.Webhook.site
    0x0290   3c2f 613e 2e0d 0a30 0d0a 0d0a                  </a>...0....
    
    2026-08-14 19:31:34.572791 VL_TEST -- 192.168.1.151.46574 -> 178.63.67.153.443: ack 615 
    0x0000   ffff ffff ffff 0009 0f09 0100 0800 4500        ..............E.
    0x0010   0028 0000 0000 4006 c2b8 c0a8 0197 b23f        .(....@........?
    0x0020   4399 b5ee 01bb 0000 00dd 0000 0267 5010        C............gP.
    0x0030   ffff 3ccf 0000                                 ..<...
    
    2026-08-14 19:31:34.573101 VL_TEST -- 192.168.1.151.46574 -> 178.63.67.153.443: rst 221 
    0x0000   ffff ffff ffff 0009 0f09 0100 0800 4500        ..............E.
    0x0010   0028 0000 0000 4006 c2b8 c0a8 0197 b23f        .(....@........?
    0x0020   4399 b5ee 01bb 0000 00dd 0000 0000 5004        C.............P.
    0x0030   ffff 3f42 0000                                 ..?B..
    
    2026-08-14 19:31:34.573125 VL_TEST -- 178.63.67.153.443 -> 192.168.1.202.46574: fin 615 
    0x0000   ffff ffff ffff 0009 0f09 0100 0800 4500        ..............E.
    0x0010   0028 0000 0000 4006 c285 b23f 4399 c0a8        .(....@....?C...
    0x0020   01ca 01bb b5ee 0000 0267 0000 0000 5001        .........g....P.
    0x0030   ffff 3d88 0000                                 ..=...
    
    2026-08-14 19:31:34.573128 VL_TEST -- 192.168.1.202.46574 -> 178.63.67.153.443: fin 221 ack 616 
    0x0000   ffff ffff ffff 0009 0f09 0100 0800 4500        ..............E.
    0x0010   0028 0000 0000 4006 c285 c0a8 01ca b23f        .(....@........?
    0x0020   4399 b5ee 01bb 0000 00dd 0000 0268 5011        C............hP.
    0x0030   ffff 3c9a 0000                                 ..<...
    
    2026-08-14 19:31:34.573131 VL_TEST -- 178.63.67.153.443 -> 192.168.1.202.46574: ack 222 
    0x0000   ffff ffff ffff 0009 0f09 0100 0800 4500        ..............E.
    0x0010   0028 0000 0000 4006 c285 b23f 4399 c0a8        .(....@....?C...
    0x0020   01ca 01bb b5ee 0000 0268 0000 00de 5010        .........h....P.
    0x0030   ffff 3c9a 0000                                 ..<...

    Short and sweet and done

    Both of these tips are closely related because I worked with both of them due to the same partner request at work, and I thought they were interesting enough for a post. Two people, both from Fortinet, helped me in exploring these tips. Maximilian Schiffner, an often-used source of knowledge, and another person who I am sure prefers to be anonymous, but if you ever needed help with authentication questions in the Fortinet ecosystem, you probably stumbled across something from him.

    I hope, dear reader, that these tips help you.

  • Quick tips: FortiClient FQDN-based split tunneling with IPsec, FortiGate Web Application Firewall URL access

    Dear reader, I have another installment of the quick tips series. This time the topics are FQDN-based split tunneling for IPsec with FortiClient and how to use the URL access feature of a FortiGate Web Application Firewall (WAF) profile.

    Models and versions:

    • FortiGate 70G on 7.6.7
    • FortiClient EMS on 7.4.7
    • FortiClient on 7.4.6
    • Windows 11 client on 25H2

    FortiClient FQDN-based split tunneling with IPsec

    One of the nice things about SSL-VPN on a FortiGate is the fact that you can do FQDN-based split tunneling with relative ease by configuring it directly in a firewall policy as the destination and performing split tunneling based on the policy destination. With IPsec, this isn’t possible, because that’s not how the protocol works. The documentation even says that you cannot use FQDN objects in the split tunnel configuration.

    Note:

    FQDNs are not supported in split-tunnel destinations. If FQDNs have been configured in the split-tunnel address group, it cannot be applied in the (set ipv4-split-include) config of the dialup IPsec.

    That won’t stop us, however.

    In order to make this happen, we can use EMS’ application-based split tunneling feature while editing a tunnel. In there, you can include or exclude Cloud and Video Streaming Applications, but also Domains, and that last point is the one that is interesting, because you can use FQDNs here.

    In this case, I am adding the FQDN of this blog, blog.guenay.at, to the configuration.

    EMS VPN FQDN-based split tunneling configuration

    The VPN configuration of a FortiGate doesn’t require any special configuration for this to work, and I have configured a regular PSK VPN with split tunneling, that routes 192.168.0.0/16 into the tunnel.

    FortiGate VPN configuration

    config vpn ipsec phase1-interface
        edit "PSK-VPN"
            set type dynamic
            set interface "wan1"
            set ike-version 2
            set keylife 28800
            set peertype any
            set net-device disable
            set mode-cfg enable
            set ipv4-dns-server1 192.168.1.169
            set proposal aes256gcm-prfsha512
            set dhgrp 31
            set eap enable
            set eap-identity send-request
            set authusrgrp "VPN_USERS"
            set client-resume enable
            set client-resume-interval 600
            set transport auto
            set ipv4-start-ip 172.16.101.1
            set ipv4-end-ip 172.16.101.20
            set ipv4-split-include "BASE-RFC1918-192"
            set psksecret ENC TaAvnptXQNmMUc/zjoy5F1CweIG8wOZUwliDjZCfvt8lr36rtJl1i95oOzgQjN22lPhP3tvZ0HOLy5XCQNtGvKO+piZ6I2wDKHVXe4Q787D5NNdrtPwonaDXZxrdyAZ5hl3r3am7zJMhWC5F5Pdz/ms5t5oew98CiSjzPUv/FHHYrxObTvi+IrxH+MW5VmUIa85ZEVlmMjY3dkVA
            set dpd-retryinterval 5
        next
    end
    config vpn ipsec phase2-interface
        edit "PSK-VPN"
            set phase1name "PSK-VPN"
            set proposal aes256gcm
            set dhgrp 31
            set keepalive enable
            set keylifeseconds 3600
        next
    end

    A policy to allow the WAN-bound traffic from the VPN is, of course, also needed for our FQDN split tunneling to work.

    FortiGate VPN WAN firewall policy

    Once the connection on FortiClient is up, we can see, in the log file of the VPN (in my case it is C:\Program Files\Fortinet\FortiClient\logs\trace\iked_ikev2_PSK-VPN7EB027B2_1.log), that split tunneling is active and per-application policies have been applied in include mode.

    [2026-08-08 18:08:27.6368270 UTC+02:00] [18216:12516] [iked  801    info] Detect split tunnel with subnet 192.168.0.0 and mask 255.255.0.0
    [2026-08-08 18:08:27.6368279 UTC+02:00] [18216:12516] [iked  806    info] split is 1
    [2026-08-08 18:08:27.6368294 UTC+02:00] [18216:12516] [iked  365    info] Applying per-application policies
    [2026-08-08 18:08:27.6368305 UTC+02:00] [18216:12516] [iked  366    info] tunnel_name = PSK-VPN
    [2026-08-08 18:08:27.6373625 UTC+02:00] [18216:12516] [iked  2511    info] Non-multiconnect vpn, set dns priority to 1
    [2026-08-08 18:08:27.6373679 UTC+02:00] [18216:12516] [iked  2531    info] dns_priority 1
    [2026-08-08 18:08:27.6373693 UTC+02:00] [18216:12516] [iked  381    info] mode = 1, dp.priority = 1
    [2026-08-08 18:08:27.6373706 UTC+02:00] [18216:12516] [iked  334    info] Applying per-application policies in include mode

    For verification, here is the route table of the VPN-connected Windows client before doing anything else.

    Route table before anything

    IPv4 Route Table
    ===========================================================================
    Active Routes:
    Network Destination        Netmask          Gateway       Interface  Metric
              0.0.0.0          0.0.0.0  192.168.100.100    192.168.100.2     25
              0.0.0.0          0.0.0.0     172.16.101.2     172.16.101.1   9001
            127.0.0.0        255.0.0.0         On-link         127.0.0.1    331
            127.0.0.1  255.255.255.255         On-link         127.0.0.1    331
      127.255.255.255  255.255.255.255         On-link         127.0.0.1    331
         172.16.101.1  255.255.255.255         On-link      172.16.101.1    257
          192.168.0.0      255.255.0.0     172.16.101.2     172.16.101.1      2
        192.168.1.202  255.255.255.255  192.168.100.100    192.168.100.2     26
        192.168.100.0    255.255.255.0         On-link     192.168.100.2    281
        192.168.100.2  255.255.255.255         On-link     192.168.100.2    281
      192.168.100.255  255.255.255.255         On-link     192.168.100.2    281
            224.0.0.0        240.0.0.0         On-link         127.0.0.1    331
            224.0.0.0        240.0.0.0         On-link     192.168.100.2    281
            224.0.0.0        240.0.0.0         On-link      172.16.101.1    257
      255.255.255.255  255.255.255.255         On-link         127.0.0.1    331
      255.255.255.255  255.255.255.255         On-link     192.168.100.2    281
      255.255.255.255  255.255.255.255         On-link      172.16.101.1    257
    ===========================================================================
    Persistent Routes:
      Network Address          Netmask  Gateway Address  Metric
              0.0.0.0          0.0.0.0     172.16.101.2  Default
    ===========================================================================

    Once the client browses to the domain listed, the following lines appear in the log file:

    [2026-08-08 18:04:47.2709530 UTC+02:00] [6764:16584] [iked  54   debug] Issue Policy: [tunnel(PSK-VPN), split_ip(46.30.213.70), split_mask(255.255.255.255), local_gwy(192.168.1.228), remote_gwy(192.168.1.202)]
    [2026-08-08 18:04:47.2735528 UTC+02:00] [6764:16584] [iked  54   debug] Issue Policy: [tunnel(PSK-VPN), split_ip(46.30.213.70), split_mask(255.255.255.255), local_gwy(192.168.1.228), remote_gwy(192.168.1.202)]

    The IP corresponds to the IP of blog.guenay.at.

    PS C:\Users\adkevin.AD.000> ping blog.guenay.at
    
    Pinging blog.guenay.at [46.30.213.70] with 32 bytes of data:

    If we check the route table after this, we see that the IP has been added and points towards the VPN interface.

    Route table after adding FQDN route

    IPv4 Route Table
    ===========================================================================
    Active Routes:
    Network Destination        Netmask          Gateway       Interface  Metric
              0.0.0.0          0.0.0.0  192.168.100.100    192.168.100.2     25
              0.0.0.0          0.0.0.0     172.16.101.2     172.16.101.1   9001
         46.30.213.70  255.255.255.255     172.16.101.2     172.16.101.1    100
            127.0.0.0        255.0.0.0         On-link         127.0.0.1    331
            127.0.0.1  255.255.255.255         On-link         127.0.0.1    331
      127.255.255.255  255.255.255.255         On-link         127.0.0.1    331
         172.16.101.1  255.255.255.255         On-link      172.16.101.1    257
          192.168.0.0      255.255.0.0     172.16.101.2     172.16.101.1      2
        192.168.1.202  255.255.255.255  192.168.100.100    192.168.100.2     26
        192.168.100.0    255.255.255.0         On-link     192.168.100.2    281
        192.168.100.2  255.255.255.255         On-link     192.168.100.2    281
      192.168.100.255  255.255.255.255         On-link     192.168.100.2    281
            224.0.0.0        240.0.0.0         On-link         127.0.0.1    331
            224.0.0.0        240.0.0.0         On-link     192.168.100.2    281
            224.0.0.0        240.0.0.0         On-link      172.16.101.1    257
      255.255.255.255  255.255.255.255         On-link         127.0.0.1    331
      255.255.255.255  255.255.255.255         On-link     192.168.100.2    281
      255.255.255.255  255.255.255.255         On-link      172.16.101.1    257
    ===========================================================================
    Persistent Routes:
      Network Address          Netmask  Gateway Address  Metric
              0.0.0.0          0.0.0.0     172.16.101.2  Default
    ===========================================================================

    The FortiGate also shows this traffic in the Forward Traffic log.

    FortiGate VPN traffic log

    If we disconnect the VPN, the restoration and removal of the routes can be seen. The domain routes aren’t specifically mentioned here, however.

    [2026-08-08 18:14:11.9604854 UTC+02:00] [13592:13376] [iked  1285    info] Restore route and interface setting...
    [2026-08-08 18:14:11.9626475 UTC+02:00] [13592:13376] [iked  1303    info] Restore routes...
    [2026-08-08 18:14:11.9627101 UTC+02:00] [13592:13376] [iked  364    info] Route del(0): Destination=192.168.1.202 Prefix=32 Nexthop=0.0.0.0 Index=3
    [2026-08-08 18:14:11.9627259 UTC+02:00] [13592:13376] [iked  364    info] Route del(0): Destination=192.168.0.0 Prefix=16 Nexthop=172.16.101.2 Index=59
    [2026-08-08 18:14:11.9627269 UTC+02:00] [13592:13376] [iked  1318    info] Restore interface default route option...
    [2026-08-08 18:14:11.9627275 UTC+02:00] [13592:13376] [iked  1330    info] Restore interface metric...
    [2026-08-08 18:14:11.9633352 UTC+02:00] [13592:13376] [iked  1345    info] Remove route and interface restore proto file(0) The operation completed successfully....

    For fun, let’s see what happens if I use the Video Streaming Applications option and add YouTube and browse to just the homepage.

    Adding YouTube routes and route table

    [2026-08-08 19:15:05.6023606 UTC+02:00] [13044:11056] [iked  54   debug] Issue Policy: [tunnel(PSK-VPN), split_ip(142.251.150.4), split_mask(255.255.255.255), local_gwy(192.168.100.2), remote_gwy(192.168.1.202)]
    [2026-08-08 19:15:05.6026407 UTC+02:00] [13044:11056] [iked  54   debug] Issue Policy: [tunnel(PSK-VPN), split_ip(142.251.151.4), split_mask(255.255.255.255), local_gwy(192.168.100.2), remote_gwy(192.168.1.202)]
    [2026-08-08 19:15:05.6028006 UTC+02:00] [13044:11056] [iked  54   debug] Issue Policy: [tunnel(PSK-VPN), split_ip(142.251.157.4), split_mask(255.255.255.255), local_gwy(192.168.100.2), remote_gwy(192.168.1.202)]
    [2026-08-08 19:15:05.6032742 UTC+02:00] [13044:11056] [iked  54   debug] Issue Policy: [tunnel(PSK-VPN), split_ip(142.251.156.4), split_mask(255.255.255.255), local_gwy(192.168.100.2), remote_gwy(192.168.1.202)]
    [2026-08-08 19:15:05.6034173 UTC+02:00] [13044:11056] [iked  54   debug] Issue Policy: [tunnel(PSK-VPN), split_ip(142.251.152.4), split_mask(255.255.255.255), local_gwy(192.168.100.2), remote_gwy(192.168.1.202)]
    [2026-08-08 19:15:05.6035045 UTC+02:00] [13044:11056] [iked  54   debug] Issue Policy: [tunnel(PSK-VPN), split_ip(142.251.155.4), split_mask(255.255.255.255), local_gwy(192.168.100.2), remote_gwy(192.168.1.202)]
    [2026-08-08 19:15:05.6035589 UTC+02:00] [13044:11056] [iked  54   debug] Issue Policy: [tunnel(PSK-VPN), split_ip(142.251.154.4), split_mask(255.255.255.255), local_gwy(192.168.100.2), remote_gwy(192.168.1.202)]
    [2026-08-08 19:15:05.6035967 UTC+02:00] [13044:11056] [iked  54   debug] Issue Policy: [tunnel(PSK-VPN), split_ip(142.251.153.4), split_mask(255.255.255.255), local_gwy(192.168.100.2), remote_gwy(192.168.1.202)]
    [2026-08-08 19:15:05.6036401 UTC+02:00] [13044:11056] [iked  54   debug] Issue Policy: [tunnel(PSK-VPN), split_ip(142.251.150.4), split_mask(255.255.255.255), local_gwy(192.168.100.2), remote_gwy(192.168.1.202)]
    [2026-08-08 19:15:05.6036984 UTC+02:00] [13044:11056] [iked  54   debug] Issue Policy: [tunnel(PSK-VPN), split_ip(142.251.151.4), split_mask(255.255.255.255), local_gwy(192.168.100.2), remote_gwy(192.168.1.202)]
    [2026-08-08 19:15:05.6037528 UTC+02:00] [13044:11056] [iked  54   debug] Issue Policy: [tunnel(PSK-VPN), split_ip(142.251.157.4), split_mask(255.255.255.255), local_gwy(192.168.100.2), remote_gwy(192.168.1.202)]
    [2026-08-08 19:15:05.6053667 UTC+02:00] [13044:11056] [iked  54   debug] Issue Policy: [tunnel(PSK-VPN), split_ip(142.251.156.4), split_mask(255.255.255.255), local_gwy(192.168.100.2), remote_gwy(192.168.1.202)]
    [2026-08-08 19:15:05.6054232 UTC+02:00] [13044:11056] [iked  54   debug] Issue Policy: [tunnel(PSK-VPN), split_ip(142.251.152.4), split_mask(255.255.255.255), local_gwy(192.168.100.2), remote_gwy(192.168.1.202)]
    [2026-08-08 19:15:05.6054551 UTC+02:00] [13044:11056] [iked  54   debug] Issue Policy: [tunnel(PSK-VPN), split_ip(142.251.155.4), split_mask(255.255.255.255), local_gwy(192.168.100.2), remote_gwy(192.168.1.202)]
    [2026-08-08 19:15:05.6054903 UTC+02:00] [13044:11056] [iked  54   debug] Issue Policy: [tunnel(PSK-VPN), split_ip(142.251.154.4), split_mask(255.255.255.255), local_gwy(192.168.100.2), remote_gwy(192.168.1.202)]
    [2026-08-08 19:15:05.6055338 UTC+02:00] [13044:11056] [iked  54   debug] Issue Policy: [tunnel(PSK-VPN), split_ip(142.251.153.4), split_mask(255.255.255.255), local_gwy(192.168.100.2), remote_gwy(192.168.1.202)]
    [2026-08-08 19:15:05.8516774 UTC+02:00] [13044:11056] [iked  54   debug] Issue Policy: [tunnel(PSK-VPN), split_ip(142.251.127.119), split_mask(255.255.255.255), local_gwy(192.168.100.2), remote_gwy(192.168.1.202)]
    [2026-08-08 19:15:05.8518532 UTC+02:00] [13044:11056] [iked  54   debug] Issue Policy: [tunnel(PSK-VPN), split_ip(142.251.110.119), split_mask(255.255.255.255), local_gwy(192.168.100.2), remote_gwy(192.168.1.202)]
    [2026-08-08 19:15:05.8519067 UTC+02:00] [13044:11056] [iked  54   debug] Issue Policy: [tunnel(PSK-VPN), split_ip(142.250.154.119), split_mask(255.255.255.255), local_gwy(192.168.100.2), remote_gwy(192.168.1.202)]
    [2026-08-08 19:15:05.8519922 UTC+02:00] [13044:11056] [iked  54   debug] Issue Policy: [tunnel(PSK-VPN), split_ip(142.251.20.119), split_mask(255.255.255.255), local_gwy(192.168.100.2), remote_gwy(192.168.1.202)]
    [2026-08-08 19:15:05.8520852 UTC+02:00] [13044:11056] [iked  54   debug] Issue Policy: [tunnel(PSK-VPN), split_ip(192.178.183.119), split_mask(255.255.255.255), local_gwy(192.168.100.2), remote_gwy(192.168.1.202)]
    [2026-08-08 19:15:05.8521251 UTC+02:00] [13044:11056] [iked  54   debug] Issue Policy: [tunnel(PSK-VPN), split_ip(142.251.13.119), split_mask(255.255.255.255), local_gwy(192.168.100.2), remote_gwy(192.168.1.202)]
    [2026-08-08 19:15:05.8521565 UTC+02:00] [13044:11056] [iked  54   debug] Issue Policy: [tunnel(PSK-VPN), split_ip(142.251.14.119), split_mask(255.255.255.255), local_gwy(192.168.100.2), remote_gwy(192.168.1.202)]
    [2026-08-08 19:15:05.8521861 UTC+02:00] [13044:11056] [iked  54   debug] Issue Policy: [tunnel(PSK-VPN), split_ip(142.251.127.119), split_mask(255.255.255.255), local_gwy(192.168.100.2), remote_gwy(192.168.1.202)]
    [2026-08-08 19:15:05.8522174 UTC+02:00] [13044:11056] [iked  54   debug] Issue Policy: [tunnel(PSK-VPN), split_ip(142.251.110.119), split_mask(255.255.255.255), local_gwy(192.168.100.2), remote_gwy(192.168.1.202)]
    [2026-08-08 19:15:05.8522468 UTC+02:00] [13044:11056] [iked  54   debug] Issue Policy: [tunnel(PSK-VPN), split_ip(142.250.154.119), split_mask(255.255.255.255), local_gwy(192.168.100.2), remote_gwy(192.168.1.202)]
    [2026-08-08 19:15:05.8522766 UTC+02:00] [13044:11056] [iked  54   debug] Issue Policy: [tunnel(PSK-VPN), split_ip(142.251.20.119), split_mask(255.255.255.255), local_gwy(192.168.100.2), remote_gwy(192.168.1.202)]
    [2026-08-08 19:15:05.8523062 UTC+02:00] [13044:11056] [iked  54   debug] Issue Policy: [tunnel(PSK-VPN), split_ip(192.178.183.119), split_mask(255.255.255.255), local_gwy(192.168.100.2), remote_gwy(192.168.1.202)]
    [2026-08-08 19:15:05.8523485 UTC+02:00] [13044:11056] [iked  54   debug] Issue Policy: [tunnel(PSK-VPN), split_ip(142.251.13.119), split_mask(255.255.255.255), local_gwy(192.168.100.2), remote_gwy(192.168.1.202)]
    [2026-08-08 19:15:05.8523786 UTC+02:00] [13044:11056] [iked  54   debug] Issue Policy: [tunnel(PSK-VPN), split_ip(142.251.14.119), split_mask(255.255.255.255), local_gwy(192.168.100.2), remote_gwy(192.168.1.202)]
    
    IPv4 Route Table
    ===========================================================================
    Active Routes:
    Network Destination        Netmask          Gateway       Interface  Metric
              0.0.0.0          0.0.0.0  192.168.100.100    192.168.100.2     25
              0.0.0.0          0.0.0.0     172.16.101.2     172.16.101.1   9001
            127.0.0.0        255.0.0.0         On-link         127.0.0.1    331
            127.0.0.1  255.255.255.255         On-link         127.0.0.1    331
      127.255.255.255  255.255.255.255         On-link         127.0.0.1    331
      142.250.154.119  255.255.255.255     172.16.101.2     172.16.101.1    100
       142.251.13.119  255.255.255.255     172.16.101.2     172.16.101.1    100
       142.251.14.119  255.255.255.255     172.16.101.2     172.16.101.1    100
       142.251.20.119  255.255.255.255     172.16.101.2     172.16.101.1    100
      142.251.110.119  255.255.255.255     172.16.101.2     172.16.101.1    100
      142.251.127.119  255.255.255.255     172.16.101.2     172.16.101.1    100
        142.251.150.4  255.255.255.255     172.16.101.2     172.16.101.1    100
        142.251.151.4  255.255.255.255     172.16.101.2     172.16.101.1    100
        142.251.152.4  255.255.255.255     172.16.101.2     172.16.101.1    100
        142.251.153.4  255.255.255.255     172.16.101.2     172.16.101.1    100
        142.251.154.4  255.255.255.255     172.16.101.2     172.16.101.1    100
        142.251.155.4  255.255.255.255     172.16.101.2     172.16.101.1    100
        142.251.156.4  255.255.255.255     172.16.101.2     172.16.101.1    100
        142.251.157.4  255.255.255.255     172.16.101.2     172.16.101.1    100
         172.16.101.1  255.255.255.255         On-link      172.16.101.1    257
          192.168.0.0      255.255.0.0     172.16.101.2     172.16.101.1      2
        192.168.1.202  255.255.255.255  192.168.100.100    192.168.100.2     26
        192.168.100.0    255.255.255.0         On-link     192.168.100.2    281
        192.168.100.2  255.255.255.255         On-link     192.168.100.2    281
      192.168.100.255  255.255.255.255         On-link     192.168.100.2    281
      192.178.183.119  255.255.255.255     172.16.101.2     172.16.101.1    100
            224.0.0.0        240.0.0.0         On-link         127.0.0.1    331
            224.0.0.0        240.0.0.0         On-link     192.168.100.2    281
            224.0.0.0        240.0.0.0         On-link      172.16.101.1    257
      255.255.255.255  255.255.255.255         On-link         127.0.0.1    331
      255.255.255.255  255.255.255.255         On-link     192.168.100.2    281
      255.255.255.255  255.255.255.255         On-link      172.16.101.1    257
    ===========================================================================
    Persistent Routes:
      Network Address          Netmask  Gateway Address  Metric
              0.0.0.0          0.0.0.0     172.16.101.2  Default
    ===========================================================================

    Lots of routes are being added, and this is reflected in the route table.

    If you use the exclude mode in the EMS VPN configuration, you see this in the log, and the routes are added to the routing table using the regular interface with your normal default gateway. Funnily enough, you don’t see these routes being added in the log.

    [2026-08-08 18:12:49.3312644 UTC+02:00] [13592:13376] [iked  801    info] Detect split tunnel with subnet 192.168.0.0 and mask 255.255.0.0
    [2026-08-08 18:12:49.3312680 UTC+02:00] [13592:13376] [iked  806    info] split is 1
    [2026-08-08 18:12:49.3312696 UTC+02:00] [13592:13376] [iked  365    info] Applying per-application policies
    [2026-08-08 18:12:49.3312705 UTC+02:00] [13592:13376] [iked  366    info] tunnel_name = PSK-VPN
    [2026-08-08 18:12:49.3327815 UTC+02:00] [13592:13376] [iked  2511    info] Non-multiconnect vpn, set dns priority to 1
    [2026-08-08 18:12:49.3327865 UTC+02:00] [13592:13376] [iked  2531    info] dns_priority 1
    [2026-08-08 18:12:49.3327878 UTC+02:00] [13592:13376] [iked  381    info] mode = 2, dp.priority = 1
    [2026-08-08 18:12:49.3327889 UTC+02:00] [13592:13376] [iked  287    info] Applying per-application policies in exclude mode
    
    IPv4 Route Table
    ===========================================================================
    Active Routes:
    Network Destination        Netmask          Gateway       Interface  Metric
              0.0.0.0          0.0.0.0  192.168.100.100    192.168.100.2     25
              0.0.0.0          0.0.0.0     172.16.101.2     172.16.101.1   9001
            127.0.0.0        255.0.0.0         On-link         127.0.0.1    331
            127.0.0.1  255.255.255.255         On-link         127.0.0.1    331
      127.255.255.255  255.255.255.255         On-link         127.0.0.1    331
      142.250.154.119  255.255.255.255  192.168.100.100    192.168.100.2    124
       142.251.13.119  255.255.255.255  192.168.100.100    192.168.100.2    124
       142.251.14.119  255.255.255.255  192.168.100.100    192.168.100.2    124
       142.251.20.119  255.255.255.255  192.168.100.100    192.168.100.2    124
      142.251.110.119  255.255.255.255  192.168.100.100    192.168.100.2    124
      142.251.127.119  255.255.255.255  192.168.100.100    192.168.100.2    124
        142.251.150.4  255.255.255.255  192.168.100.100    192.168.100.2    124
        142.251.151.4  255.255.255.255  192.168.100.100    192.168.100.2    124
        142.251.152.4  255.255.255.255  192.168.100.100    192.168.100.2    124
        142.251.153.4  255.255.255.255  192.168.100.100    192.168.100.2    124
        142.251.154.4  255.255.255.255  192.168.100.100    192.168.100.2    124
        142.251.155.4  255.255.255.255  192.168.100.100    192.168.100.2    124
        142.251.156.4  255.255.255.255  192.168.100.100    192.168.100.2    124
        142.251.157.4  255.255.255.255  192.168.100.100    192.168.100.2    124
         172.16.101.1  255.255.255.255         On-link      172.16.101.1    257
          192.168.0.0      255.255.0.0     172.16.101.2     172.16.101.1      2
        192.168.1.202  255.255.255.255  192.168.100.100    192.168.100.2     26
        192.168.100.0    255.255.255.0         On-link     192.168.100.2    281
        192.168.100.2  255.255.255.255         On-link     192.168.100.2    281
      192.168.100.255  255.255.255.255         On-link     192.168.100.2    281
      192.178.183.119  255.255.255.255  192.168.100.100    192.168.100.2    124
            224.0.0.0        240.0.0.0         On-link         127.0.0.1    331
            224.0.0.0        240.0.0.0         On-link     192.168.100.2    281
            224.0.0.0        240.0.0.0         On-link      172.16.101.1    257
      255.255.255.255  255.255.255.255         On-link         127.0.0.1    331
      255.255.255.255  255.255.255.255         On-link     192.168.100.2    281
      255.255.255.255  255.255.255.255         On-link      172.16.101.1    257
    ===========================================================================
    Persistent Routes:
      Network Address          Netmask  Gateway Address  Metric
              0.0.0.0          0.0.0.0     172.16.101.2  Default
    ===========================================================================

    This method of FQDN-based split tunneling is a nice compromise when transitioning from SSL-VPN to IPsec. Don’t forget the Cloud Applications option in this, because it offers a few often-used services and acts like the other options.

    FortiGate Web Application Firewall URL access

    Due to a blog post about FortiClient EMS Let’s Encrypt Security that I wrote for my employer, I was told about the URL access feature that lives inside the WAF profile of a FortiGate, which I wasn’t aware of before. Outside of the CLI reference, I cannot find any documentation of this feature for a FortiGate, only for FortiWeb, but it is relatively self-explanatory. Still, let’s see how to configure it.

    Note: The URL access feature is CLI-only, but you should still enable Web Application Firewall in Feature Visibility, because otherwise the option to assign the profile in a policy does not show up in the GUI. Also, you can do much the same with a web filter profile, which I do in the post above, so the WAF option is not the only method to achieve this; it just saves on another profile.

    On the CLI, you can configure a basic URL access block as follows:

    config waf profile
        edit "WAF_URL-ACCESS"
            config url-access
                edit 1
                    set address "ubuntu-ws-1.ad.labdomain.com"
                    set action block
                    set log enable
                    config access-pattern
                        edit 1
                            set srcaddr "all"
                            set pattern ".*/login.html$"
                            set regex enable
                        next
                    end
                next
            end
        next
    end

    Let’s go through it.

    The address option determines the backend host that is being protected, and it is an actual address object. I have tested it with the types ipmask and fqdn, and both worked here, assuming your DNS resolution is correct for the FQDN option.

    action has the block option, which does exactly that, bypass, which allows the connection and skips all other WAF scanning, and permit, which allows the connection and continues with WAF scanning.

    log is for logging; who would have thought.

    In the access-pattern entry itself, we can set the srcaddr option, which determines to which source address this should apply, so we can target only specific addresses. Again, this is an actual address object.

    Enabling regex does that, and because I prefer regex-matching over everything else, I have it enabled. Disabling it probably does a simple match on the URL, but I didn’t test this because if regex is available, I will use only that.

    The actual regex pattern is used in the pattern option. In my case, I am checking for every URL path that ends in /login.html.

    That’s it on how to configure the feature.

    Note that there is no implicit deny rule here, so if you don’t have an entry for a pattern, it is allowed. Entries are matched top down, so keep that in mind.

    Assign the WAF profile in a policy, and if you perform deep inspection, either with an SSL/SSH profile or a virtual server, you can also control access in HTTPS traffic.

    FortiGate WAF firewall policy

    If I test the URL https://ubuntu-ws-1.ad.labdomain.com/login.html on my client, I get the block, as expected, and I see this in my FortiGate Web Application Firewall log.

    Client WAF URL access block
    WAF URL access block log

    Browsing to any other URL, like https://ubuntu-ws-1.ad.labdomain.com/index.html or just https://ubuntu-ws-1.ad.labdomain.com, does not lead to a block, because I never explicitly mentioned this in my WAF profile and I also don’t have a catch-all entry.

    If you ever need to debug this feature, then you need to know that the WAF feature uses the wad process, and the category http is the important one.

    diagnose wad debug enable category http
    diagnose wad debug enable level info
    diagnose debug enable

    In the debugs, you can see the request coming in and the URL access being matched.

    FortiGate Web Application Firewall debug

    [I][p:1758][s:11239][r:33554552] wad_dump_http_request             :3134  hreq=0x7f9df0d8e0 Received request from client: 192.168.100.2:56531
    
    GET /login.html HTTP/1.1
    Host: ubuntu-ws-1.ad.labdomain.com
    Connection: keep-alive
    Pragma: no-cache
    Cache-Control: no-cache
    sec-ch-ua: "Not=A?Brand";v="99", "Microsoft Edge";v="151", "Chromium";v="151"
    sec-ch-ua-mobile: ?0
    sec-ch-ua-platform: "Windows"
    Upgrade-Insecure-Requests: 1
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.0.0 Safari/537.36 Edg/151.0.0.0
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
    Sec-Fetch-Site: none
    Sec-Fetch-Mode: navigate
    Sec-Fetch-User: ?1
    Sec-Fetch-Dest: document
    Accept-Encoding: gzip, deflate, br, zstd
    Accept-Language: en-GB,en;q=0.9,en-US;q=0.8
    Cookie: session=expiry=1786211939675194
    
    [I][p:1758][s:11239][r:33554552] wad_http_parse_host               :2019  host=[28]ubuntu-ws-1.ad.labdomain.com
    [I][p:1758][s:11239][r:33554552] wad_http_str_canonicalize         :2621  enc=0 path=/login.html len=11 changes=0
    [I][p:1758][s:11239][r:33554552] wad_http_req_detect_special       :14589 captive_portal detected: false, preflight=(null)
    [I][p:1758][s:11239][r:33554552] wad_http_req_domain_front_chk     :9751  chk uri host res=pass (is_abs=0 http_encap=1 connect=0): ubuntu-ws-1.ad.labdomain.com
    [I][p:1758][s:11239][r:33554552] wad_http_waf_access_control       :1242
    [I][p:1758][s:11239][r:33554552] wad_http_waf_access_control       :1269  WAF matched URL access action=1
    [I][p:1758][s:11239][r:33554552] __wad_http_build_replmsg_resp     :933   Generating replacement message. repmsg_id 77
    [I][p:1758][s:11239][r:33554552] wad_dump_fwd_http_resp            :3155  hreq=0x7f9df0d8e0 Forward response from Internal:
    
    HTTP/1.1 403 Forbidden
    Connection: close
    Content-Type: text/html
    Cache-Control: no-cache
    X-Frame-Options: SAMEORIGIN
    X-XSS-Protection: 1; mode=block
    X-Content-Type-Options: nosniff
    Content-Security-Policy: frame-ancestors 'self'
    Content-Length: 35039

    If you want a configuration that allows a specific URL path and blocks all others, you can configure it like this:

    config waf profile
        edit "WAF_URL-ACCESS"
            config url-access
                edit 1
                    set address "ubuntu-ws-1.ad.labdomain.com"
                    set log enable
                    config access-pattern
                        edit 1
                            set srcaddr "all"
                            set pattern ".*/login.html$"
                            set regex enable
                        next
                    end
                next
                edit 2
                    set address "ubuntu-ws-1.ad.labdomain.com"
                    set action block
                    set log enable
                    config access-pattern
                        edit 1
                            set srcaddr "all"
                            set pattern ".*"
                            set regex enable
                        next
                    end
                next
            end
        next
    end

    And that’s that feature explained.

    Short and sweet and done

    The configuration for both things in this quick tips installment isn’t anything to write home about, as it should be for this series, but I haven’t seen much on either of these things, so hopefully this helps at least you, dear reader.