# Feature Request: limit\_req support in the stream module (currently http-only)

**URL:** <https://community.nginx.org/t/feature-request-limit-req-support-in-the-stream-module-currently-http-only/10819>\
**Category:** NGINX\
**Tags:** config, reverse-proxy\
**Created:** [September 3, 2026, 4:04pm UTC](https://community.nginx.org/t/feature-request-limit-req-support-in-the-stream-module-currently-http-only/10819 "2026-09-03T16:04:23Z")\
**Posts on this page:** 3\
**Page:** 1

<div class="post-metadata">

**Author:** ![AzureCoral43](https://avatars.discourse-cdn.com/v4/letter/a/c57346/32.png) [@AzureCoral43](https://community.nginx.org/u/AzureCoral43)\
**Post date:** [September 3, 2026, 4:04pm UTC](https://community.nginx.org/t/feature-request-limit-req-support-in-the-stream-module-currently-http-only/10819/1 "2026-09-03T16:04:23Z")

</div>

_Please use this template for troubleshooting questions._

**My issue:** I’m looking for a way to rate-limit _connection attempts per second_ from a single source IP on a `stream` (TCP/TLS) listener — equivalent to what `limit_req` does for `http`. The `stream` module only offers `limit_conn`, which caps _concurrent_ connections, not the _rate_ of new attempts over time. Because of this, a client can perform repeated failed TLS handshakes (e.g., invalid/missing client certificate) in rapid succession without ever tripping `limit_conn`, since each failed handshake closes quickly and frees the slot immediately. Each attempt still costs real CPU (TCP accept + TLS negotiation + client-cert chain verification) before being rejected — a low-and-slow handshake-flood vector that neither `limit_conn` nor `ssl_handshake_timeout` mitigates.

Is there any existing way (including NGINX Plus) to achieve this, and if not, is bringing `limit_req`-style rate limiting to the `stream` module on any roadmap?

**How I encountered the problem:** We proxy a TLS-terminated, mutually-authenticated TCP protocol (MQTT) through the `stream` module with `ssl_verify_client on`. While reviewing our per-source-IP abuse protections, I noticed our `http`/WebSocket listener’s `limit_req` correctly throttles all attempts per IP regardless of success/failure, but the equivalent protection isn’t possible on the `stream` listener since `limit_req` doesn’t exist there.

**Solutions I’ve tried:**  
Confirmed `ngx_stream_limit_req_module` does not exist in nginx (checked official docs and changelog — only `ngx_stream_limit_conn_module` is available for `stream`).

- Currently using `limit_conn` (concurrency cap) plus `ssl_handshake_timeout 10s` to bound per-handshake cost, but this doesn’t limit attempt frequency.

**Version of NGINX or NGINX adjacent software (e.g. NGINX Gateway Fabric):** nginx 1.31.2

**Deployment environment:** Docker container (Debian/trixie based image), nginx as a TLS-terminating TCP proxy in front of an MQTT broker.

**Minimal NGINX config to reproduce your issue (preferably running on** [NGINX Playground | tech-playground.com](https://tech-playground.com/playgrounds/nginx) **for ease of debugging, and if not as a code block):** _(Tip → Run `nginx -T` to print your entire NGINX config to your terminal.)_

**NGINX access/error log:** _(Tip → You can usually find the logs in the `/var/log/nginx` directory.)_

---

<div class="post-metadata">

**Author:** ![ac000](https://drob5swvtjqxg.cloudfront.net/user_avatar/community.nginx.org/ac000/32/207_2.png) [@ac000](https://community.nginx.org/u/ac000)\
**Post date:** [September 3, 2026, 4:56pm UTC](https://community.nginx.org/t/feature-request-limit-req-support-in-the-stream-module-currently-http-only/10819/2 "2026-09-03T16:56:06Z")

</div>

You’re right that there’s no `limit_req` equivalent in the `stream` module today —`limit_conn` only caps concurrency, and as you’ve found, fast-closing failed handshakes never occupy a slot long enough to trip it.

For per-source **connection-rate** limiting in front of a TCP/TLS listener, the canonical answer is to do it at the packet-filter layer rather than in nginx. That’s not a workaround so much as the right tool: it drops the offending SYNs in the kernel before nginx accepts the connection, so you pay nothing for the TCP accept, TLS negotiation or client-cert verification — the exact costs you’re trying to avoid.

**nftables** (modern; rate-limit new connections per source IP to your listener port, e.g. 8883):

`# drop new connections from a source exceeding 10/s (burst 20) to 8883`

`tcp dport 8883 ct state new \ `  
` meter mqtt-rate { ip saddr limit rate over 10/second burst 20 packets } \`  
` drop`

**iptables** (legacy) equivalent with `hashlimit`:

`iptables -A INPUT -p tcp --dport 8883 --syn -m conntrack --ctstate NEW \`  
` -m hashlimit --hashlimit-name mqtt-rate --hashlimit-mode srcip \`  
` --hashlimit-above 10/second --hashlimit-burst 20 -j DROP`

Tune the rate/burst to your legitimate client behaviour. You can keep your existing `limit_conn` and `ssl_handshake_timeout` as complementary concurrency/duration bounds.

If you specifically want to react to failed handshakes (e.g. repeated invalid client certs), pairing this with **fail2ban** watching the stream error log for `client SSL certificate verify error` lines — and banning at the firewall — gives you an adaptive, longer-duration block on top of the steady-state rate cap.

A native `ngx_stream_limit_req_module` would be a reasonable enhancement and isn’t unfeasible, but nothing like it exists in nginx (open source or Plus) at present, and for a pure handshake-flood defence the firewall approach is both simpler and cheaper.

---

<div class="post-metadata">

**Author:** ![system](https://drob5swvtjqxg.cloudfront.net/user_avatar/community.nginx.org/system/32/19_2.png) [@system](https://community.nginx.org/u/system)\
**Post date:** [October 3, 2026, 4:56pm UTC](https://community.nginx.org/t/feature-request-limit-req-support-in-the-stream-module-currently-http-only/10819/3 "2026-10-03T16:56:36Z")

</div>

Closed on October 3, 2026, 4:56pm UTC

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.
