Long connections cut by something in the middle
Clients report a connection dropping every few minutes and reconnecting cleanly. That regularity usually means a session table expiring somewhere in the path.
Home routers, cloud NAT gateways and some firewalls keep idle TCP sessions for around 300 seconds. Past that the entry is reclaimed and the next packet goes nowhere.
Make your probes shorter than the timer:
net.ipv4.tcp_keepalive_time = 120
net.ipv4.tcp_keepalive_intvl = 30
net.ipv4.tcp_keepalive_probes = 3
An application-level heartbeat is better where you control both ends; TCP keepalive only fires on an idle socket and defaults to two hours. Set it on both sides — it is the middle that forgets, not one endpoint.