{"id":1496,"date":"2026-10-26T22:15:36","date_gmt":"2026-10-26T22:15:36","guid":{"rendered":"https:\/\/bestdedicatedwebhostingserver.com\/blog\/?p=1496"},"modified":"2026-10-26T22:15:36","modified_gmt":"2026-10-26T22:15:36","slug":"proxy-protocol-real-ip-game-node-haproxy","status":"publish","type":"post","link":"https:\/\/bestdedicatedwebhostingserver.com\/blog\/proxy-protocol-real-ip-game-node-haproxy\/","title":{"rendered":"PROXY Protocol on a Game Front-End: Recovering Real Player IPs Behind HAProxy"},"content":{"rendered":"<div style=\"padding:20px\">\n<p>Putting HAProxy in front of a game node is the standard way to hide origin IPs and absorb floods, and it works until you look at the server log and every player connects from <code>10.0.0.14<\/code>. That single line breaks per-IP bans, connection rate limits, geo rules, and any abuse forensics you had. The cause is structural: a TCP proxy terminates the client connection and opens a new one to the backend, so the backend&#8217;s view of the peer is the proxy, not the player. PROXY protocol exists to fix exactly that, but only if both ends speak it.<\/p>\n<h2>Why Plain TCP Forwarding Destroys Source IPs<\/h2>\n<p>HAProxy in <code>mode tcp<\/code> is a bidirectional byte pump. It accepts the client&#8217;s socket, establishes a separate socket to the backend, and copies bytes. The backend sees the proxy&#8217;s source address and a new ephemeral port. There is no field in a raw TCP stream to carry the original address, and no amount of header inspection will recover it after the fact.<\/p>\n<p>The usual workarounds all fail for game traffic. Reading <code>X-Forwarded-For<\/code> does not apply \u2014 game protocols are binary UDP or TCP, not HTTP. Trusting the proxy to rewrite connection metadata works only for protocols with in-band identity, which most game handshakes do not have. Logging a session identifier and joining it with HAProxy logs is possible but gives you the mapping minutes late and not at all for UDP.<\/p>\n<h2>PROXY Protocol: v1 and v2<\/h2>\n<p>PROXY protocol prepends a single header to the connection before any game bytes. The backend parses it, learns the real source, then continues reading the normal stream. Two versions exist, and the choice matters operationally.<\/p>\n<table>\n<thead>\n<tr>\n<th>Aspect<\/th>\n<th>v1<\/th>\n<th>v2<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Format<\/td>\n<td>Text line<\/td>\n<td>Binary, 16-byte signature<\/td>\n<\/tr>\n<tr>\n<td>Signature<\/td>\n<td><code>PROXY TCP4 ...<\/code><\/td>\n<td><code>0D 0A 0D 0A 00 0D 0A 51 55 49 54 0A<\/code><\/td>\n<\/tr>\n<tr>\n<td>Address families<\/td>\n<td>TCP4 \/ TCP6 only<\/td>\n<td>TCP\/UDP, IPv4\/IPv6, UNIX<\/td>\n<\/tr>\n<tr>\n<td>UDP support<\/td>\n<td>No<\/td>\n<td>Yes (with datagram framing)<\/td>\n<\/tr>\n<tr>\n<td>TLV extensions<\/td>\n<td>No<\/td>\n<td>Yes<\/td>\n<\/tr>\n<tr>\n<td>Overhead<\/td>\n<td>~40-80 bytes<\/td>\n<td>16 + 4 + length<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Game traffic is UDP in almost every modern title, so v2 is the version you need. v1 is fine for a Minecraft TCP front-end and is easier to debug because you can see it in a hex dump as plain text.<\/p>\n<pre><code># HAProxy front-end sending PROXY v2 to the backend\nfrontend game_in\n  bind *:25565\n  mode tcp\n  default_backend mc_nodes\n\nbackend mc_nodes\n  mode tcp\n  server node1 10.0.0.14:25566 send-proxy-v2 check\n  server node2 10.0.0.15:25566 send-proxy-v2 check backup<\/code><\/pre>\n<p>One rule that catches everyone: the backend listener must either expect PROXY headers on every connection or on none. Mixing causes the backend to interpret a PROXY header as game data, and you get protocol errors or a parser crash on the first packet.<\/p>\n<h2>Which Game Servers Read It Natively<\/h2>\n<p>Native support is thinner than the web world, where every reverse proxy speaks it.<\/p>\n<ul>\n<li><strong>Minecraft Java<\/strong> \u2014 supported on modern Paper\/Purpur builds via <code>proxy-protocol: true<\/code> in <code>config\/paper-global.yml<\/code>. Vanilla does not support it; vanilla expects a Velocity\/BungeeCord handshake instead.<\/li>\n<li><strong>Velocity and BungeeCord<\/strong> \u2014 these are the proxy layer themselves, so they do not need PROXY protocol; they forward identity in their own handshake. Use PROXY protocol only between HAProxy and an edge Velocity node.<\/li>\n<li><strong>Source-engine servers (CS2, TF2, Rust via Steam networking)<\/strong> \u2014 no native PROXY protocol. Identity comes from Steam auth tickets, and per-IP logic must come from the game&#8217;s own logs or a plugin.<\/li>\n<li><strong>ARK, Valheim, Palworld<\/strong> \u2014 no native support; UDP backends need the proxy to inject the header and the server to be patched, which most do not allow.<\/li>\n<li><strong>Rust (Oxide\/uMod)<\/strong> \u2014 plugins can read a forwarded address from connection metadata, but the vanilla binary will not.<\/li>\n<\/ul>\n<p>Where a server cannot read the header, the pragmatic options are a plugin that accepts it, a mod that trusts a shared secret plus an address header on the game port, or accepting that per-IP logic has to run at the proxy layer instead of the game layer.<\/p>\n<h2>Preserving Real IP Behind Minecraft-Style Proxies<\/h2>\n<p>The common topology has three hops: player to HAProxy, HAProxy to Velocity, Velocity to Paper. PROXY protocol only solves the first hop. Velocity passes the player&#8217;s address to Paper inside its own login handshake, so as long as Velocity is configured to trust the HAProxy-supplied address, the chain holds end to end.<\/p>\n<pre><code># Paper: accept PROXY header from the front-end\nproxy-protocol: true\nvelocity:\n  enabled: true\n  online-mode: true\n  secret: \"&lt;32-char-shared-secret&gt;\"\n\n# Velocity: only trust forwarded addresses from the HAProxy IPs\n# velocity.toml\n[advanced]\nhaproxy-protocol = true\n# and in the firewall, allow the Velocity port only from HAProxy<\/code><\/pre>\n<p>The security condition is non-negotiable: the Velocity port must be firewalled to HAProxy&#8217;s addresses only. An exposed Velocity port with <code>haproxy-protocol<\/code> enabled lets anyone forge a source address and bypass IP bans in a single packet. The same applies to Paper&#8217;s port \u2014 bind it to the private network and let nothing else reach it.<\/p>\n<h2>UDP Game Nodes and the v2 Header<\/h2>\n<p>For UDP, the model changes. HAProxy&#8217;s UDP support can prepend a v2 header to each datagram, but many game servers cannot parse it and will simply discard the packet. In practice the UDP front-end is more often used for filtering and rate limiting than for IP recovery. If you need per-player IPs on a UDP title, the reliable path is session tracking at the proxy: log <code>client_ip:client_port -&gt; backend_ip:backend_port<\/code> with a session table, and join those records to your game logs by timestamp and port.<\/p>\n<h2>Troubleshooting: When the IP Is Still Wrong<\/h2>\n<ul>\n<li><strong>Header sent, backend ignores it.<\/strong> Check that the backend build actually has PROXY support compiled in \u2014 the option is often off by default and fails silently, logging the proxy&#8217;s IP as the peer.<\/li>\n<li><strong>Header sent twice.<\/strong> Two proxies in series each prepending a header. Only the last hop before a header-aware backend should send one, or use <code>accept-proxy<\/code> on the intermediary.<\/li>\n<li><strong>All players show the same IP.<\/strong> <code>send-proxy<\/code> missing from the server line, or the wrong backend selected by a health check.<\/li>\n<li><strong>Truncated v2 header.<\/strong> Some middleboxes or a MTU issue split the first packet. Verify with a capture on the backend interface.<\/li>\n<li><strong>v1 mixed with v2.<\/strong> A single listener configured for v2 fed by a v1 sender produces a signature mismatch and an immediate close.<\/li>\n<\/ul>\n<pre><code># Capture on the backend to confirm the header is present\ntcpdump -i eth1 -X -c 5 'tcp port 25566'\n# v2 starts with the 12-byte signature: 0d0a0d0a000d0a515549540a\n# v1 looks like: PROXY TCP4 203.0.113.9 10.0.0.14 51234 25566\n\n# HAProxy stats: confirm the right backend and check retries\necho \"show stat\" | socat stdio \/var\/run\/haproxy.sock | cut -d, -f1,2,18,19<\/code><\/pre>\n<p>If the capture shows the header and the backend still logs the proxy address, the problem is the backend configuration, not the network. If the capture shows no header, the HAProxy server line is missing <code>send-proxy-v2<\/code>, or the backend is reached through a different path than the one you are capturing.<\/p>\n<h2>Design Rules That Keep It Working<\/h2>\n<p>Terminate PROXY protocol as close to the game process as the software allows. Keep every header-aware listener on a private network with a firewall allowlist. Log both the proxy&#8217;s observation and the backend&#8217;s parsed address for a week after any change, and diff them \u2014 a silent fallback to the proxy IP is the most common regression. Treat the proxy as a trust boundary: rate limits, geo rules, and bans belong at HAProxy where the real address is always visible.<\/p>\n<p>For nodes at the origin side of this topology, <a href=\"https:\/\/bestdedicatedwebhostingserver.com\/\">bare metal game server hosting<\/a> gives you the private-network layout and static addresses this design assumes. For sizing the proxy tier itself, see the <a href=\"https:\/\/bestdedicatedwebhostingserver.com\/\">game server hosting reviews and comparisons<\/a> \u2014 HAProxy needs very little CPU but a surprising amount of conntrack table for a busy join port.<\/p>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Putting HAProxy in front of a game node is the standard way to hide origin IPs and absorb floods, and it works until you look at the server log and&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"iawp_total_views":1,"footnotes":""},"categories":[13],"tags":[],"class_list":["post-1496","post","type-post","status-publish","format-standard","hentry","category-game-server-setup"],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v26.1 (Yoast SEO v26.1) - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>PROXY Protocol on a Game Front-End: Recovering Real Player IPs Behind HAProxy - Best Dedicated Web Hosting Server Blog<\/title>\n<meta name=\"description\" content=\"Putting HAProxy in front of a game node is the standard way to hide origin IPs and absorb floods, and it works until you look at the server log and every\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/bestdedicatedwebhostingserver.com\/blog\/proxy-protocol-real-ip-game-node-haproxy\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"PROXY Protocol on a Game Front-End: Recovering Real Player IPs Behind HAProxy\" \/>\n<meta property=\"og:description\" content=\"PROXY Protocol on a Game Front-End: Recovering Real Player IPs Behind HAProxy\" \/>\n<meta property=\"og:url\" content=\"https:\/\/bestdedicatedwebhostingserver.com\/blog\/proxy-protocol-real-ip-game-node-haproxy\/\" \/>\n<meta property=\"og:site_name\" content=\"Best Dedicated Web Hosting Server Blog\" \/>\n<meta property=\"article:published_time\" content=\"2026-10-26T22:15:36+00:00\" \/>\n<meta name=\"author\" content=\"bestdedicatedwebhostingserver\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@bestdedicatedwebhostingserver\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"bestdedicatedwebhostingserver\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"6 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\/\/bestdedicatedwebhostingserver.com\/blog\/proxy-protocol-real-ip-game-node-haproxy\/\",\"url\":\"https:\/\/bestdedicatedwebhostingserver.com\/blog\/proxy-protocol-real-ip-game-node-haproxy\/\",\"name\":\"PROXY Protocol on a Game Front-End: Recovering Real Player IPs Behind HAProxy - Best Dedicated Web Hosting Server Blog\",\"isPartOf\":{\"@id\":\"https:\/\/bestdedicatedwebhostingserver.com\/blog\/#website\"},\"datePublished\":\"2026-10-26T22:15:36+00:00\",\"author\":{\"@id\":\"https:\/\/bestdedicatedwebhostingserver.com\/blog\/#\/schema\/person\/6eb5f9ea35033fe8e67df44397e089b1\"},\"description\":\"Putting HAProxy in front of a game node is the standard way to hide origin IPs and absorb floods, and it works until you look at the server log and every\",\"breadcrumb\":{\"@id\":\"https:\/\/bestdedicatedwebhostingserver.com\/blog\/proxy-protocol-real-ip-game-node-haproxy\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/bestdedicatedwebhostingserver.com\/blog\/proxy-protocol-real-ip-game-node-haproxy\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/bestdedicatedwebhostingserver.com\/blog\/proxy-protocol-real-ip-game-node-haproxy\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/bestdedicatedwebhostingserver.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"PROXY Protocol on a Game Front-End: Recovering Real Player IPs Behind HAProxy\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/bestdedicatedwebhostingserver.com\/blog\/#website\",\"url\":\"https:\/\/bestdedicatedwebhostingserver.com\/blog\/\",\"name\":\"Best Dedicated Web Hosting Server Blog\",\"description\":\"\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/bestdedicatedwebhostingserver.com\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\/\/bestdedicatedwebhostingserver.com\/blog\/#\/schema\/person\/6eb5f9ea35033fe8e67df44397e089b1\",\"name\":\"bestdedicatedwebhostingserver\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/bestdedicatedwebhostingserver.com\/blog\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/462a07aa37399bdb149e5f91dc3cd8906656bc4c7ed391a3b6f0199c5d2ab964?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/462a07aa37399bdb149e5f91dc3cd8906656bc4c7ed391a3b6f0199c5d2ab964?s=96&d=mm&r=g\",\"caption\":\"bestdedicatedwebhostingserver\"},\"sameAs\":[\"https:\/\/bestdedicatedwebhostingserver.com\/blog\",\"https:\/\/x.com\/bestdedicatedwebhostingserver\"],\"url\":\"https:\/\/bestdedicatedwebhostingserver.com\/blog\/author\/bestdedicatedwebhostingserver\/\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"PROXY Protocol on a Game Front-End: Recovering Real Player IPs Behind HAProxy - Best Dedicated Web Hosting Server Blog","description":"Putting HAProxy in front of a game node is the standard way to hide origin IPs and absorb floods, and it works until you look at the server log and every","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/bestdedicatedwebhostingserver.com\/blog\/proxy-protocol-real-ip-game-node-haproxy\/","og_locale":"en_US","og_type":"article","og_title":"PROXY Protocol on a Game Front-End: Recovering Real Player IPs Behind HAProxy","og_description":"PROXY Protocol on a Game Front-End: Recovering Real Player IPs Behind HAProxy","og_url":"https:\/\/bestdedicatedwebhostingserver.com\/blog\/proxy-protocol-real-ip-game-node-haproxy\/","og_site_name":"Best Dedicated Web Hosting Server Blog","article_published_time":"2026-10-26T22:15:36+00:00","author":"bestdedicatedwebhostingserver","twitter_card":"summary_large_image","twitter_creator":"@bestdedicatedwebhostingserver","twitter_misc":{"Written by":"bestdedicatedwebhostingserver","Est. reading time":"6 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/bestdedicatedwebhostingserver.com\/blog\/proxy-protocol-real-ip-game-node-haproxy\/","url":"https:\/\/bestdedicatedwebhostingserver.com\/blog\/proxy-protocol-real-ip-game-node-haproxy\/","name":"PROXY Protocol on a Game Front-End: Recovering Real Player IPs Behind HAProxy - Best Dedicated Web Hosting Server Blog","isPartOf":{"@id":"https:\/\/bestdedicatedwebhostingserver.com\/blog\/#website"},"datePublished":"2026-10-26T22:15:36+00:00","author":{"@id":"https:\/\/bestdedicatedwebhostingserver.com\/blog\/#\/schema\/person\/6eb5f9ea35033fe8e67df44397e089b1"},"description":"Putting HAProxy in front of a game node is the standard way to hide origin IPs and absorb floods, and it works until you look at the server log and every","breadcrumb":{"@id":"https:\/\/bestdedicatedwebhostingserver.com\/blog\/proxy-protocol-real-ip-game-node-haproxy\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/bestdedicatedwebhostingserver.com\/blog\/proxy-protocol-real-ip-game-node-haproxy\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/bestdedicatedwebhostingserver.com\/blog\/proxy-protocol-real-ip-game-node-haproxy\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/bestdedicatedwebhostingserver.com\/blog\/"},{"@type":"ListItem","position":2,"name":"PROXY Protocol on a Game Front-End: Recovering Real Player IPs Behind HAProxy"}]},{"@type":"WebSite","@id":"https:\/\/bestdedicatedwebhostingserver.com\/blog\/#website","url":"https:\/\/bestdedicatedwebhostingserver.com\/blog\/","name":"Best Dedicated Web Hosting Server Blog","description":"","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/bestdedicatedwebhostingserver.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/bestdedicatedwebhostingserver.com\/blog\/#\/schema\/person\/6eb5f9ea35033fe8e67df44397e089b1","name":"bestdedicatedwebhostingserver","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/bestdedicatedwebhostingserver.com\/blog\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/462a07aa37399bdb149e5f91dc3cd8906656bc4c7ed391a3b6f0199c5d2ab964?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/462a07aa37399bdb149e5f91dc3cd8906656bc4c7ed391a3b6f0199c5d2ab964?s=96&d=mm&r=g","caption":"bestdedicatedwebhostingserver"},"sameAs":["https:\/\/bestdedicatedwebhostingserver.com\/blog","https:\/\/x.com\/bestdedicatedwebhostingserver"],"url":"https:\/\/bestdedicatedwebhostingserver.com\/blog\/author\/bestdedicatedwebhostingserver\/"}]}},"_links":{"self":[{"href":"https:\/\/bestdedicatedwebhostingserver.com\/blog\/wp-json\/wp\/v2\/posts\/1496","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/bestdedicatedwebhostingserver.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/bestdedicatedwebhostingserver.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/bestdedicatedwebhostingserver.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/bestdedicatedwebhostingserver.com\/blog\/wp-json\/wp\/v2\/comments?post=1496"}],"version-history":[{"count":1,"href":"https:\/\/bestdedicatedwebhostingserver.com\/blog\/wp-json\/wp\/v2\/posts\/1496\/revisions"}],"predecessor-version":[{"id":1497,"href":"https:\/\/bestdedicatedwebhostingserver.com\/blog\/wp-json\/wp\/v2\/posts\/1496\/revisions\/1497"}],"wp:attachment":[{"href":"https:\/\/bestdedicatedwebhostingserver.com\/blog\/wp-json\/wp\/v2\/media?parent=1496"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/bestdedicatedwebhostingserver.com\/blog\/wp-json\/wp\/v2\/categories?post=1496"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/bestdedicatedwebhostingserver.com\/blog\/wp-json\/wp\/v2\/tags?post=1496"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}