OpenVidu 3.8.0: nf_conntrack: table full, dropping packet after upgrading

Hi, so i have this problem, where after upgrading from openvidu 3.4.1, to 3.8.0 after few hours I can’t access my VM, because of nf_conntrack: table full, dropping packet error.

After some digging, I found out that caddy is creating lots of internal connections, propably health checks, and in nf_conntrack it looks like that:
sudo awk -F’src=’ ‘{print $2}’ /proc/net/nf_conntrack | awk ‘{print $1}’ | sort | uniq -c | sort -rn | head -20
3363 127.0.0.1
829 10.10.99.15
829 10.10.99.14
67 10.10.99.133
1 10.10.30.110

and the number of connections are getting bigger, to the point where nf_conntrack table is getting full, and then I have to restart vm for it to work again. The stragest thing is, that at the moment when i was digging, there was no one, that would create room, or any connection to this instance of openvidu. I’ve also tried to debug this issue with claude, and this was its response:

“caddy’s active health-checks (interval: 5s) against 127.0.0.1:7780, 127.0.0.1:8080 and 127.0.0.1:9100, combined with the layer4 TCP-proxy hops (:443 → :5656 → :7880) introduced for the single-port architecture, open new loopback TCP connections that are never closed or returned to a reusable pool. Because both endpoints of each connection are the same live caddy process, TCP keepalive never fires (the peer always ACKs), so the connections sit ESTABLISHED indefinitely — bounded only by the kernel’s nf_conntrack_tcp_timeout_established (5 days by default) on one side, and by caddy’s RLIMIT_NOFILE on the other.”

I dont really know if it actualy is the root cause for any of this, but I’m here to provide any additional info if necessary to resolve this issue.

P.S These are the containers names, that I have running:
egress
dashboard
redis
openvidu
ingress
caddy
mongo
operator

And no, I don’t use openvidu-meet, as this is for an custom application, where I don’t need it

Thanks for the report. I will take a look

Hi @grzegorz.fuks,

Thanks for the detailed report.

I tried to reproduce it on a 3.8.0 Single Node Community deployment with a setup similar to yours (the same containers you listed: no OpenVidu Meet, no observability, no MinIO), and I couldn’t. With no clients connected, the conntrack table stays flat for hours. Caddy’s health checks (against 7780, 8080 and 9100) reuse a single pooled connection each, so they are not what fills the table. I also tested plain HTTPS requests, load balancer health checks, LiveKit load tests and clients that disappear without closing the connection, and every connection was released.

What did change after 3.4.1 is how much each connection costs in conntrack. TURN over TLS now shares port 443, so every TLS connection to 443 goes through two internal loopback hops (443 → 5656 → 7880) and uses 3 conntrack entries instead of 2. Your numbers match that pattern almost exactly: 3363 ≈ 2 × (829 + 829) + a small baseline. So the 127.0.0.1 entries are most likely the internal hops of connections coming from 10.10.99.14 and 10.10.99.15.

What we need to find out is which of these two cases you are hitting:

  • short-lived connections arriving at a high rate. These show up as TIME_WAIT entries, which expire after 120 s;
  • connections that are kept open and never closed. These show up as ESTABLISHED or CLOSE_WAIT entries.

Could you share the following?

  1. What are 10.10.99.14 and 10.10.99.15: load balancers, reverse proxies, your application servers…? If there is something in front of OpenVidu, how is it configured? I’m interested in whether it does TCP passthrough or HTTP, its timeouts, and its health checks and their interval.

  2. The output of:

sysctl net.netfilter.nf_conntrack_max net.netfilter.nf_conntrack_count
  1. The output of these commands, run twice, about one hour apart:
sudo awk '{s=($3=="tcp")?$6:"-"; src=dst=dp=""; for(i=1;i<=NF;i++){if(src==""&&$i~/^src=/)src=substr($i,5); else if(dst==""&&$i~/^dst=/)dst=substr($i,5); else if(dp==""&&$i~/^dport=/)dp=substr($i,7)} print $3, s, src, "->", dst":"dp}' /proc/net/nf_conntrack | sort | uniq -c | sort -rn | head -40
sudo ls /proc/$(pgrep -x caddy)/fd | wc -l
sudo ss -tanp | grep '"caddy"' | awk '{print $1}' | sort | uniq -c

The first one groups the conntrack entries by protocol, state, source and destination port. The other two show how many file descriptors and sockets Caddy has open.

With that we should be able to tell what is going on.

We couldn’t reproduce the table filling up with normal traffic, and we haven’t seen it in other deployments. But we found two behaviours in Caddy’s layer4 module that let a client that never closes its connection keep it ESTABLISHED forever. That includes its conntrack entry, which by default expires after 5 days:

On top of that, 3.8.0 sends every HTTPS connection through an extra internal hop (443 → 127.0.0.1:5656 → 7880), which doubles the conntrack entries per connection. That fits the 127.0.0.1 entries in your snapshot.

Since the fix involves configuration changes, it will probably ship in 3.10.0: Caddy will send the FIN, close half-closed connections after 30 s, and the port 5656 server won’t be needed anymore.

On 3.8.0 you can already remove the 5656 hop with a configuration change only. We tested it on 3.8.0: HTTP and TURN over TLS keep working, and each connection uses half the conntrack entries.

In /opt/openvidu/config/caddy.yaml (make a backup first), in the public server, replace this handler:

              - handler: proxy
                upstreams:
                  - dial:
                    - 127.0.0.1:${openvidu.CADDY_TCP_DEMUX_PORT:?mandatory}

with:

              - handler: subroute
                routes:
                  - match:
                      - regexp:
                          count: 6
                          pattern: "^[\x00-\x3F](?s:..?.?)\x21\x12$"
                    handle:
                      - handler: proxy
                        upstreams:
                          - dial:
                            - {{getNetworkIPByInterface "docker0"}}:${openvidu.LIVEKIT_TURN_TLS_INTERNAL_PORT:?mandatory}
                  - handle:
                      - handler: proxy
                        upstreams:
                          - dial:
                            - 127.0.0.1:${openvidu.CADDY_HTTP_INTERNAL_PORT:?mandatory}

Then delete the whole tcp-demux: server, from its # TCP demuxer for TURN and HTTP traffic comment down to just before http:, and run sudo systemctl restart openvidu. Afterwards, sudo ss -ltn | grep 5656 should print nothing.

To give the table more room in the meantime:

sudo sysctl -w net.netfilter.nf_conntrack_max=524288
sudo sysctl -w net.netfilter.nf_conntrack_tcp_timeout_established=86400

Finally, to confirm the cause: what are 10.10.99.14 and 10.10.99.15 (a load balancer? monitoring?), and what does this print while the table grows?

sudo conntrack -L -p tcp -s 10.10.99.14 2>/dev/null | awk '{print $4, $8}' | sort | uniq -c | sort -rn | head