Skip to main content
GeorgeZhong
Staff & Editor
Staff & Editor
June 23, 2026

Technical Note: Potential cause of HTTPS POST TLS 1.3 connection failures through Web Application Firewall (WAF) profile in FortiGate

  • June 23, 2026
  • 0 replies
  • 203 views

Description

This article describes a scenario in which an HTTPS POST request using TLS 1.3 to a web server behind a FortiGate may fail due to the handling of the 'HTTP/1.1 Expect: 100-continue' mechanism when both Web Application Firewall (WAF) and SSL Deep Inspection are enabled.

Scope

FortiGate, Web Application Firewall.

Solution

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:

  1. The client sends the HTTP request headers, including Expect: 100-continue.

  2. The server responds with:

  3. HTTP/1.1 100 Continue

  4. 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.Â