Bug #8377
openOPNsense Suricata divert-to mode causes aborted HTTPS response streams (Axios / Node.js) – works with responseType: stream
Description
Describe the bug¶
When using Suricata with the new divert-to mode in OPNsense, some HTTPS requests made with Node.js (Axios) fail with response stream aborted.
The same request works normally when:
- `divert-to` is disabled (`none`)
- Axios is configured with `responseType: "stream"`
- requesting a static file (e.g. `/robots.txt`) instead of an HTML page
Issue in OPNsense: https://github.com/opnsense/core/issues/9956
Network Setup
Cloud VM (uptime-kuma)
│
Internet
│
OPNsense (Suricata IPS enabled, divert-to mode)
│
Reverse Proxy
│
Backend Application
To Reproduce¶
The request is sent from an external cloud VM to a public service behind OPNsense.
- Deploy a service behind OPNsense that is reachable via HTTPS through a reverse proxy.
- Enable Suricata IPS on the WAN interface using divert-to mode.
- From an external system (e.g. cloud VM), run the following Node.js request:
node -e ' const axios = require("axios"); const https = require("https"); const http = require("http"); const httpsAgent = new https.Agent({ rejectUnauthorized: true, maxCachedSessions: 0 }); const httpAgent = new http.Agent({ maxCachedSessions: 0 }); axios({ url: "https://example.com", method: "get", timeout: 10000, maxRedirects: 10, httpAgent, httpsAgent, headers: { "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" } }) .then(res => { console.log("STATUS", res.status); }) .catch(err => { console.error("ERROR", err.message); }); '
Output: ERROR response stream aborted
Suricata disabled¶
When i set divert-to to None it works as expected:
node -e ' const axios = require("axios"); const https = require("https"); const http = require("http"); const httpsAgent = new https.Agent({ rejectUnauthorized: true, maxCachedSessions: 0 }); const httpAgent = new http.Agent({ maxCachedSessions: 0 }); axios({ url: "https://example.com", method: "get", timeout: 10000, maxRedirects: 10, httpAgent, httpsAgent, headers: { "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" } }) .then(res => { console.log("STATUS", res.status); }) .catch(err => { console.error("ERROR", err.message); }); '
Output: STATUS 200
Response Type: stream¶
If set 'responseType: "stream",' the request works as expected even with suricata divert-to enabled:
node -e ' const axios = require("axios"); const https = require("https"); const http = require("http"); const httpsAgent = new https.Agent({ rejectUnauthorized: true, maxCachedSessions: 0 }); const httpAgent = new http.Agent({ maxCachedSessions: 0 }); axios({ url: "https://example.com", method: "get", responseType: "stream", timeout: 10000, maxRedirects: 10, httpAgent, httpsAgent, headers: { "A
ccept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" } }) .then(res => { console.log("STATUS", res.status); }) .catch(err => { console.error("ERROR", err.message); }); '
Output: STATUS 200
Expected behavior¶
I would expect STATUS 200 with suricata divert-to enabled.
Relevant log files¶
There are no relevant logs and no alerts.
Environment¶
- OPNsense version: 26.1.4
- Suricata version: 8.0.3
- Client: Node.js + Axios
- Test system: containerized uptime-kuma
Updated by Victor Julien 1 day ago
Can you share any Suricata logs generated for this traffic? Would also be good record a pcap to see if that gives any clues.
Updated by ABC DEF about 15 hours ago
Hello Victor,
I sent you the requested packet captures via email (I don’t want to share them publicly). I’m not sure which Suricata logs might be helpful. As far as I could see when looking through them, there wasn’t really anything that stood out or seems related to this request.
Updated by ABC DEF about 14 hours ago
This issue only occurs when divert-to is used on a WAN-side pf rule, i.e., when the passing traffic is still encrypted.
If I add the divert-to setting to the rule from the reverse proxy to the web server (where traffic is unencrypted) instead, the request works correctly and the eve logging shows:
{"timestamp":"2026-03-14T09:47:41.161486+0100","flow_id":1411451134062175,"event_type":"http","src_ip":"ip.from.reverse.proxy","src_port":54814,"dest_ip":"ip.from.web.server","dest_port":8080,"proto":"TCP","ip_v":4,"pkt_src":"wire/pcap","tx_id":0,"http":{"hostname":"example.com","url":"/","http_user_agent":"axios/0.30.3","xff":"::ffff:ip.from.requesting.host","http_content_type":"text/html","http_method":"GET","protocol":"HTTP/1.1","status":200,"length":123929,"request_headers":[{"name":"accept","value":"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"},{"name":"user-agent","value":"axios/0.30.3"},{"name":"host","value":"example.com"},{"name":"x-forwarded-proto","value":"https"},{"name":"x-forwarded-for","value":"::ffff:ip.from.requesting.host"}],"response_headers":[{"name":"Date","value":"Sat, 14 Mar 2026 08:47:41 GMT"},{"name":"Server","value":"Apache/2.4.66 (Debian)"},{"name":"X-Powered-By","value":"PHP/8.3.30"},{"name":"Link","value":"<https://example.com/wp-json/>; rel=\"https://api.w.org/\""},{"name":"Vary","value":"Accept-Encoding"},{"name":"Transfer-Encoding","value":"chunked"},{"name":"Content-Type","value":"text/html; charset=UTF-8"}]}}
{"timestamp":"2026-03-14T09:47:48.431785+0100","flow_id":1133209534922269,"event_type":"http","src_ip":"ip.from.reverse.proxy","src_port":35892,"dest_ip":"ip.from.web.server","dest_port":8080,"proto":"TCP","ip_v":4,"pkt_src":"wire/pcap","tx_id":0,"http":{"hostname":"example.com","url":"/robots.txt","http_user_agent":"Uptime-Kuma/2.2.1","xff":"::ffff:ip.from.requesting.host","http_content_type":"text/plain","http_method":"GET","protocol":"HTTP/1.1","status":200,"length":168,"request_headers":[{"name":"accept","value":"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.9"},{"name":"user-agent","value":"Uptime-Kuma/2.2.1"},{"name":"host","value":"example.com"},{"name":"x-forwarded-proto","value":"https"},{"name":"x-forwarded-for","value":"::ffff:ip.from.requesting.host"}],"response_headers":[{"name":"Date","value":"Sat, 14 Mar 2026 08:47:48 GMT"},{"name":"Server","value":"Apache/2.4.66 (Debian)"},{"name":"X-Powered-By","value":"PHP/8.3.30"},{"name":"X-Robots-Tag","value":"noindex, follow"},{"name":"Link","value":"<https://example.com/wp-json/>; rel=\"https://api.w.org/\""},{"name":"Vary","value":"Accept-Encoding"},{"name":"Content-Length","value":"168"},{"name":"Content-Type","value":"text/plain; charset=utf-8"}]}}