Files
minecraft/docs/tcpshield.md
Aryan Singh dff2b5a9c4 Add TCPShield / Proxy Protocol support
Adds enforced Proxy Protocol config and docs: a patch-set (config/patches/patch-set.json) that enables proxy protocol, whitelists TCPShield, and sets proxy/direct IPs; exposes PATCH_DEFINITIONS and mounts ./config/patches in docker-compose.yml; adds a featured "Proxy Protocol Support" entry to the dashboard mods page; and adds docs/tcpshield.md describing setup, verification, security model, and first-boot behavior.
2026-07-11 18:51:27 -05:00

3.8 KiB

TCPShield (DDoS protection + real player IPs)

TCPShield sits in front of the server: players connect to your TCPShield domain, and your real server IP stays hidden and DDoS-protected. The catch: without extra setup, every connection appears to come from TCPShield's proxies, so the Security page, IP bans, and connection logs are useless.

TCPShield's own RealIP plugin is Bukkit/Bungee/Velocity-only — it does not exist for Fabric. The working path for this stack is PROXY protocol v2 plus the Proxy Protocol Support Fabric mod.

Setup

  1. TCPShield panel → your backend → enable Proxy Protocol.

  2. Mods page → Recommended → add Proxy Protocol Support → Apply (or append proxy-protocol-support to MODRINTH_PROJECTS in .env and docker compose up -d mc).

  3. Nothing else — the mod's configuration is enforced automatically on every boot by config/patches/patch-set.json (see below). Restart once after installing so the patch applies:

    docker compose restart mc
    

Verifying it works

Watch the startup log (docker compose logs -f mc). You must see all three:

Proxy Protocol Support is enabled!
TCPShield integration enabled! Fetching official IPs...
Successfully added 2 TCPShield IPs to the trusted proxy list.

Then join through your TCPShield domain — the console shows your real IP, and docker compose ps shows mc healthy.

How the config is managed (don't hand-edit!)

The mod rewrites config/proxy_protocol_support.json at every startup, which silently clobbers hand edits. Instead, the server image applies config/patches/patch-set.json from this repo before every server start, enforcing:

Setting Value Why
enableProxyProtocol true decode PROXY protocol headers
whitelistTCPShieldServers true auto-fetch TCPShield's proxy IP ranges — no hardcoding, survives their infra changes
proxyServerIPs 127.0.0.1 (TCPShield's ranges are added automatically at runtime)
directAccessIPs localhost (127.0.0.1, ::1) + private ranges (10/8, 172.16/12, 192.168/16) container healthcheck + LAN/panel access

To change a value, edit the patch file in the repo and docker compose up -d mc.

First-boot note: on a brand-new install the mod's config file doesn't exist yet when patches run, so the very first boot uses mod defaults; every boot after that is patched. One docker compose restart mc after first install settles it.

Security model

PROXY protocol headers carry no signature, so anyone who finds your raw server IP could spoof client IPs — the mod prevents this with default-deny:

  • PROXY headers are only accepted from TCPShield's published ranges (auto-fetched)
  • Direct, header-less connections are only accepted from localhost + LAN
  • Everything else → REJECTED unauthorized direct connection in the log

So occasional REJECTED lines from random public IPs are the wall working — that's scanner bots hitting your raw IP and bouncing. A REJECTED ... /0:0:0:0:0:0:0:1 (IPv6 localhost) every few seconds means the container healthcheck is being blocked — that's exactly what the ::1 entry in the patch prevents; if you see it, the patch didn't apply (check docker compose logs mc | head -50).

For belt-and-braces, also enable TCPShield's Advanced Firewall (panel → Network → Firewall), and consider host-firewalling port 25565 to TCPShield's ranges (curl tcpshield.com/v4) once everything works.

Gotchas

  • Friends must connect via the TCPShield domain — direct-IP connections from outside your LAN are rejected by design once this is active.
  • If real players start bouncing with REJECTED, TCPShield may have changed their IP ranges and the auto-fetch failed at boot — restart the server so it re-fetches.