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
VJ Updated by Victor Julien about 2 months 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.
AD Updated by ABC DEF about 2 months 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.
AD Updated by ABC DEF about 2 months 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"}]}}