Context
The Torrust Tracker Demo (deployed via this deployer on Hetzner) has been suffering from intermittent UDP tracker downtime on newTrackon. After ruling out firewall issues, container crashes, and UDP buffer overflows, the root cause was confirmed:
The default nf_conntrack kernel table fills up under sustained UDP tracker load, causing silent packet drops (nf_conntrack: table full, dropping packet in dmesg).
Docker bridge networking creates a conntrack entry for every UDP packet it forwards via DNAT. With the default UDP stream timeout of 120 s, entries accumulate faster than they expire at tracker request rates. Once the table is full (default ceiling: 262144 entries), new packets are silently dropped — no application log, no socket error counter.
This was confirmed on the live demo server today (2026-04-20):
net.netfilter.nf_conntrack_max = 262144
net.netfilter.nf_conntrack_count = 262144 ← 100% full
[27834.670811] nf_conntrack: nf_conntrack: table full, dropping packet
... (2478 occurrences)
The same issue was documented in the original Torrust demo on DigitalOcean: torrust/torrust-demo#26. That instance reached 99.2% UDP uptime after the same fix. It has also been independently documented by others running UDP trackers behind Docker: ftorrent/open README.
Full investigation: torrust/torrust-tracker-demo#21.
Fix Applied
Three sysctl parameters were tuned and persisted at /etc/sysctl.d/99-conntrack.conf on the demo server:
# Raise table ceiling. Default 65536–262144 fills up under tracker load.
# Each entry uses ~300 bytes; 1 M entries ≈ 300 MB.
net.netfilter.nf_conntrack_max = 1048576
# Reduce UDP stream timeout. Default 120 s; tracker exchanges complete in ms.
# Reducing from 120 s to 15 s cuts steady-state table size by ~8x.
net.netfilter.nf_conntrack_udp_timeout_stream = 15
# Reduce one-way UDP timeout. Default 30 s.
net.netfilter.nf_conntrack_udp_timeout = 10
Reboot persistence trap: net.netfilter.* sysctl keys only exist after the nf_conntrack kernel module is loaded. Docker loads it when it starts, but systemd applies sysctl configs at boot before Docker runs — so settings in sysctl.d are silently skipped on reboot. Fix: pre-load the module via /etc/modules-load.d/conntrack.conf:
Status: Fix applied 2026-04-20. Still monitoring newTrackon to confirm UDP uptime recovery. This issue should be updated once results are in.
Proposed Documentation Change
New user-guide page: docs/user-guide/kernel-tuning.md
This fix applies to any provider where Ubuntu is the base OS and the tracker runs behind Docker bridge networking. It should not live only in the Hetzner section.
Suggested location: docs/user-guide/kernel-tuning.md (or docs/user-guide/ubuntu-kernel-tuning.md), cross-linked from:
Suggested content for the new page
- Why this is needed — Docker DNAT creates conntrack entries; under UDP tracker load the default table fills
- The calculation —
requests/s × timeout_s = minimum entries needed; at 400 req/s × 120 s = 48000 minimum, at 1500 req/s × 120 s = 180000 — dangerously close to the 262144 default
- The three sysctl parameters — what each does and why each value was chosen
- The reboot trap — why
sysctl.d alone is not enough; the module pre-load file
- Verification commands — how to confirm the fix is in place and holding:
# Table usage — count should stay well below max
sysctl net.netfilter.nf_conntrack_count net.netfilter.nf_conntrack_max
# Active drops — should be 0 or not growing
sudo dmesg | grep "nf_conntrack: table full" | wc -l
# Verify settings survive after reboot
cat /proc/sys/net/netfilter/nf_conntrack_max
# Should show 1048576, not the default
References
Context
The Torrust Tracker Demo (deployed via this deployer on Hetzner) has been suffering from intermittent UDP tracker downtime on newTrackon. After ruling out firewall issues, container crashes, and UDP buffer overflows, the root cause was confirmed:
The default
nf_conntrackkernel table fills up under sustained UDP tracker load, causing silent packet drops (nf_conntrack: table full, dropping packetindmesg).Docker bridge networking creates a conntrack entry for every UDP packet it forwards via DNAT. With the default UDP stream timeout of 120 s, entries accumulate faster than they expire at tracker request rates. Once the table is full (default ceiling: 262144 entries), new packets are silently dropped — no application log, no socket error counter.
This was confirmed on the live demo server today (2026-04-20):
The same issue was documented in the original Torrust demo on DigitalOcean: torrust/torrust-demo#26. That instance reached 99.2% UDP uptime after the same fix. It has also been independently documented by others running UDP trackers behind Docker: ftorrent/open README.
Full investigation: torrust/torrust-tracker-demo#21.
Fix Applied
Three sysctl parameters were tuned and persisted at
/etc/sysctl.d/99-conntrack.confon the demo server:Reboot persistence trap:
net.netfilter.*sysctl keys only exist after thenf_conntrackkernel module is loaded. Docker loads it when it starts, but systemd applies sysctl configs at boot before Docker runs — so settings insysctl.dare silently skipped on reboot. Fix: pre-load the module via/etc/modules-load.d/conntrack.conf:Proposed Documentation Change
New user-guide page:
docs/user-guide/kernel-tuning.mdThis fix applies to any provider where Ubuntu is the base OS and the tracker runs behind Docker bridge networking. It should not live only in the Hetzner section.
Suggested location:
docs/user-guide/kernel-tuning.md(ordocs/user-guide/ubuntu-kernel-tuning.md), cross-linked from:Suggested content for the new page
requests/s × timeout_s = minimum entries needed; at 400 req/s × 120 s = 48000 minimum, at 1500 req/s × 120 s = 180000 — dangerously close to the 262144 defaultsysctl.dalone is not enough; the module pre-load fileReferences