Skip to content

docs: add kernel tuning guide for UDP tracker behind Docker bridge networking #463

@josecelano

Description

@josecelano

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:

nf_conntrack

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

  1. Why this is needed — Docker DNAT creates conntrack entries; under UDP tracker load the default table fills
  2. The calculationrequests/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
  3. The three sysctl parameters — what each does and why each value was chosen
  4. The reboot trap — why sysctl.d alone is not enough; the module pre-load file
  5. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    documentationImprovements or additions to documentation

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions