Topology:
Client -> FortiGate (WAF + SSL deep inspection enabled)Â -> Web Server (172.16.81.6).
In this deployment, the FortiGate is configured as a Virtual Server that provides HTTPS access to the backend web server.
Virtual Server configuration:
config firewall vip
edit "Test Web Server"
set type server-load-balance
set server-type https
set extip 172.16.80.6
set extintf "port1"
set h2-support disable
set extport 443
config realservers
edit 1
set ip 172.16.81.6
set port 443
set verify-cert disable
next
end
set ssl-mode full
set ssl-certificate "server"
set ssl-min-version tls-1.2
next
end
This Virtual Server is referenced as the destination object in the corresponding firewall policy to allow inbound HTTPS access to the backend web server. As part of a standard FortiGate WAF deployment, both a Web Application Firewall (WAF) profile and an SSL Deep Inspection profile are applied to the policy.
Reference: Technical Tip: How to configure a Web Application Firewall (WAF) to protect a web server behind FortiGate
Firewall Policy Configuration:
config firewall policy
edit 79
set name "Test Web Services"
set srcintf "port1"
set dstintf "port2"
set action accept
set srcaddr "all"
set dstaddr "Test Web Server"
set schedule "always"
set service "HTTPS"
set utm-status enable
set inspection-mode proxy
set ssl-ssh-profile "Protect Server"
set waf-profile "default"
set logtraffic all
set logtraffic-start enable
next
end
SSL Deep Inspection Profile:
The SSL Deep Inspection profile is configured with a server certificate replacement certificate ('Protecting Server' certificate) as shown below.
config firewall ssl-ssh-profile
edit "Protect Server"
config ssl
set inspect-all deep-inspection
set min-allowed-ssl-version tls-1.2
end
config https
set ports 443
set quic inspect
end
config ftps
end
config imaps
end
config pop3s
end
config smtps
end
config ssh
set ports 22
set status disable
end
config dot
set status disable
set quic inspect
end
set server-cert-mode replace
set server-cert "server"
next
end
Under normal conditions, this configuration successfully handles most HTTPS traffic destined for the backend web server. However, connection failures may occur when the client initiates an HTTPS POST request containing the HTTP header 'Expect: 100-contine'.
According to the HTTP/1.1 specification, the purpose of the Expect: 100-continue header is to allow a client to defer transmission of the POST request body until the server confirms that it is willing to accept it. This prevents unnecessary transmission of large payloads when the server is likely to reject the request.
The typical exchange is as follows:
The client sends the HTTP request headers, including Expect: 100-continue. The server responds with: HTTP/1.1 100 Continue The client then sends the POST request body.
In the affected scenario, the client remains in a waiting state until timeout because it never receives the expected 100 Continue response from the server. However, in this situation, the client side may be stuck in the state of waiting for the response of '100-continue' message from server side until timeout.Â
By looking at the WAD debug on the FortiGate, it shows that the FortiGate successfully receives and parses the POST request header containing Expect: 100-continue. However, the request is not forwarded to the backend server, preventing the server from generating the required 100 Continue response.
[I]2026-03-27 15:47:57.097571 [p:2315][s:94737414][r:4604907] wad_dump_http_request :2915 hreq=0x7f93c4fd7ce8 Received request from client: 58.179.218.18:49387
POST /Services/Testpayment.asmx HTTP/1.1
Content-Type: text/xml; charset=utf-8
SOAPAction: http://test.org/request
Host: test.payment.com
Transfer-Encoding: chunked
Expect: 100-continue
Connection: Keep-Alive
[V]2026-03-27 15:47:57.097577 [p:2315][s:94737414][r:4604907] wad_http_marker_uri :1270 path=/Services/Testpayment.asmx len=23
[V]2026-03-27 15:47:57.097582 [p:2315][s:94737414][r:4604907] wad_http_parse_host :1649 host_len=25
[I]2026-03-27 15:47:57.097586 [p:2315][s:94737414][r:4604907] wad_http_parse_host :1681 host=[25]test.payment.com
[I]2026-03-27 15:47:57.097591 [p:2315][s:94737414][r:4604907] wad_http_str_canonicalize :2196 enc=0 path=/Services/Payments.asmx len=23 changes=0
[V]2026-03-27 15:47:57.097594 [p:2315][s:94737414][r:4604907] wad_http_normalize_uri :2586 host_len=25 path_len=23 query_len=0
[I]2026-03-27 15:47:57.097598 [p:2315][s:94737414][r:4604907] wad_http_req_detect_special :16102 captive_portal detected: false, preflight=(null)
[V]2026-03-27 15:47:57.097606 [p:2315][s:94737414][r:4604907] wad_http_req_exec_act :14489 request(0x7f93c4fd7ce8), intercept(pass), block(0)
[V]2026-03-27 15:47:57.097610 [p:2315][s:94737414][r:4604907] wad_http_req_exec_act :14578 dst_addr_type=1 wc_nontp=0 sec_web=1 web_cache=0 req_bypass=0
[I]2026-03-27 15:47:57.097617 [p:2315][s:94737414][r:4604907] wad_http_urlfilter_check :386 uri_norm=1 inval_host=0 inval_url=0 scan-hdr/body=1/1 url local=0 block=0 user-cat=0 allow=0 ftgd=0 keyword=0 wisp=0
[I]2026-03-27 15:47:57.097622 [p:2315][s:94737414][r:4604907] wad_http_req_proc_waf :1375 req=0x7f93c4fd7ce8 ssl.deep_scan=1 proto=10 exempt=0 waf=(nil) body_len=18446744073709551615 ua= skip_scan=0
[I]2026-03-27 15:47:57.097626 [p:2315][s:94737414][r:4604907] wad_http_waf_access_control :1239
[I]2026-03-27 15:47:57.097630 [p:2315][s:94737414][r:4604907] wad_http_req_proc_waf_body :1334
[V]2026-03-27 15:47:57.097633 [p:2315][s:94737414][r:4604907] wad_http_strm_read_body :963 http stream 0x7f93c5009e28 body_type=1 body_len=18446744073709551615
[V]2026-03-27 15:47:57.097637 [p:2315][s:94737414][r:4604907] wad_http_clt_read_sync :2027 hs=0x7f93c5009df8 pause=(0/0x0) ret=1 execute=wad_http_strm_read_chunk_size
[V]2026-03-27 15:47:57.097643 [p:2315][s:94737414][r:4604907] wad_tcp_port_window_adjust :475 tcp_port 0x7f93c56a9438 window-type auto-tuning set 1 SNDBUF 131072 RCVBUF 528000 CLAMP 8388608
[I]2026-03-27 15:47:57.097716 [p:2315][s:94737414][r:4604907] wad_tcp_port_flush :1582 sock 155 write (1,223,223) n_written=223 tcp_port 0x7f93c56a9438
[2315] write [(1,223) (17 03 03 00 da 3b 14 66 07 88 40 da 2b 24 79 7b 01 7d e7 d7 01 47 9f 7d a1 4f 29 ec 9e 2b 4a 1d 04 e2 1e 72 f5 d3 28 58 af be e9 26 fe a6 a5 65 44 2f 30 dc 44 36 7a 49 34 f5 39 7e cc b0 d1 27 a6 a1 66 24 49 33 de bf 03 02 d
2 72 e7 7a 52 7c 7e 4e 2b 98 1a 91 62 92 8a d1 45 5d 73 73 f0 d5 05 f2 40 27 38 c0 38 6e 3f 7f 65 bb 8c cd ef 46 88 48 f3 ce 17 21 5f 28 e4 c3 05 26 95 eb c1 8e 9d f9 36 10 27 50 90 5e 1d 16 74 f7 f1 eb 1c 0e 2b 9b ac ab bc 5d 8d 01 2f f0 eb bc c
9 cd 07 49 5f ef 53 91 14 de 4d 9d 27 24 fa 8b 3a a3 b2 71 7d f7 27 e7 51 1f d7 1a 13 be 87 3e fa 90 33 37 13 e5 82 78 71 68 f2 fc 36 a7 bb f7 f5 6c d7 f7 1b 9e 75 25 1a 6d 51 53 ce 0e 0c 2d c8 ec b3 )(.....;.f..@.+$y{.}...G.}.O)..+J....r..(X...&
...eD/0.D6zI4.9~...'..f$I3.....r.zR|~N+...b...E]ss....@'8.8n?.e....F.H...!_(...&......6.'P.^..t.....+....]../......I_.S...M.'$..:..q}.'.Q......>..37...xqh..6....l....u%.mQS...-...)]
2026-03-27 15:47:57.104808 [p:2316] ipsapp ses 4895038 send msg 10628 len 2464 dir 0 count 1
2026-03-27 15:47:57.104825 [p:2316] ipsapp ses 4895038 send writing msg 0 of 1, for length 2464
2026-03-27 15:47:57.104831 [p:2316] ipsapp us 122 write (1,3016)
[V]2026-03-27 15:47:57.105260 [p:2316] wad_mem_c_free :176 freeing memory not in bucket
[V]2026-03-27 15:47:57.105273 [p:2316] wad_mem_c_malloc :138 size 65568 exceeds max_elm_size (18392); not using bucket
2026-03-27 15:47:57.105287 [p:2316] ipsapp us 122 read (0,65536)
2026-03-27 15:47:57.105294 [p:2316] ipsapp ses 4895038 msg 10627 eval response dir 0 act 0 app 0 proto 0 tlv_len 0
2026-03-27 15:47:57.105298 [p:2316] ipsapp ses 4895038 msg 10628 eval response dir 0 act 0 app 0 proto 1 tlv_len 0
This behavior occurs because the WAF profile expects the complete HTTP request, including both the request headers and body, before forwarding the request to the backend server. However, when the client sends a request containing the 'Expect: 100-continue header', it waits for a '100-Continue response' before transmitting the request body.Â
This creates a deadlock condition, as the FortiGate waits for the body before forwarding the request, and the client waits for the '100 Continue response' before sending the body. As a result, neither side progresses and the client eventually experiences a timeout.
This issue happens in the FortiOS version before FortiOS v7.6.5. From FortiOS v7.6.5, starting from FortiOS v7.6.5, the WAF processing logic has been enhanced to handle this scenario correctly. When the FortiGate receives an HTTPS POST request containing 'the Expect: 100-continue header', the WAF inspects the request headers and forwards them to the backend server after validation, without waiting for the request body. This allows the backend server to return the 100 Continue response and prevents the deadlock condition described above.Â
|