Guide
How to track where your Minecraft players come from
A Minecraft client has no referrer, no link to click and no cookie. When someone joins your server, the only clue to how they found you is the address they typed into the multiplayer screen. The client sends that address to your server in the first packet of the connection, the handshake.
So the method is simple: give every creator, ad and server list its own address, and record which address each player used the first time they joined. This guide covers the DNS, the Velocity side, how to read and store the hostname, and the traps that quietly break it.
Give every source its own address
- The player types it
alex.example.netfrom a creator's video - DNS points it at your proxyA CNAME to
play.example.net - The handshake carries itVelocity reads the address the client sent
- You store it onceAt the player's first join, for good
Pick one main address for everyone who finds you on their own, such as play.example.net, and make a new subdomain for every place that sends players. The subdomain is the tracking link. Nothing about the server changes, and players see nothing unusual.
| Source | Address you give out |
|---|---|
| Players who find you on their own | play.example.net |
| A YouTube creator called Alex | alex.example.net |
| A sponsored server list slot | list1.example.net |
| Your Discord announcements | discord.example.net |
| A TikTok ad campaign | tt.example.net |
- Keep names short and easy to say out loud. Creators read them on stream, and players type them by hand.
- Name the address after the deal, not the platform. Two creators on YouTube need two addresses.
- Never delete an address you have published. Players save it in their server list and keep using it for months.
Set up the DNS on Cloudflare
Every tracking address has to reach the same proxy. How you do that depends on the port your proxy listens on.
Proxy on the default port, 25565
Add one CNAME record per source that points at your main address. When the main address moves to a new IP, every tracking address follows it. A wildcard A record works too, and saves you a record for each new creator.
; One CNAME per source, all pointing at the main address
alex.example.net. 300 IN CNAME play.example.net.
list1.example.net. 300 IN CNAME play.example.net.
discord.example.net. 300 IN CNAME play.example.net.
; Or one wildcard A record at the proxy's IP
*.example.net. 300 IN A 203.0.113.10In Cloudflare, set the proxy status of these records to DNS only, the grey cloud. Cloudflare's own docs say records for non-HTTP services should be DNS only, and Minecraft traffic is not HTTP.
Proxy on another port: SRV records
If your proxy listens on another port, players need an SRV record so they do not have to type the port. Here is the trap: with an SRV record, the client sends the SRV target to your proxy, not the name the player typed. The Velocity FAQ says it directly: Velocity matches on the hostname the client sends, which is resolved before the connection, not necessarily the address the player types.
If every SRV record points at the same target, every source looks the same. Give each source its own target name instead:
; Each source gets its own SRV target on the proxy's port
_minecraft._tcp.alex.example.net. 300 IN SRV 0 5 25577 alex-srv.example.net.
_minecraft._tcp.list1.example.net. 300 IN SRV 0 5 25577 list1-srv.example.net.
; Each target is a plain name for the proxy
alex-srv.example.net. 300 IN CNAME play.example.net.
list1-srv.example.net. 300 IN CNAME play.example.net.Cloudflare's SRV form asks for the same parts: the name, priority, weight, port and target. Your proxy then sees alex-srv.example.net for players who typed alex.example.net, so that target is the name to label as Alex.
Test every address before you publish it
Join once through each new address with a test account and check that it shows up as its own source. It takes a minute and catches a wrong record before a creator's video goes live.
Velocity: forced hosts or one entry point
Velocity has a feature called forced hosts that routes players by the address they used. It is a routing tool, not a tracking tool, and you do not need it to track sources. Any address that reaches the proxy sends the player to the servers in try, and the address still arrives with the connection.
Forced hosts help when a source should land somewhere specific. If a creator plays your Skyblock mode, send their viewers straight there:
[servers]
lobby = "127.0.0.1:30066"
skyblock = "127.0.0.1:30067"
try = ["lobby"]
[forced-hosts]
"skyblock.example.net" = ["skyblock"]
"alex.example.net" = ["skyblock"]With SRV records, put the SRV target in forced hosts, for the reason above.
Record the hostname on the proxy, not on the backends. A backend behind Velocity does not see the address the player typed, so the proxy is the one place that knows it. If you run several proxies behind a load balancer, each sees its own share of players, so each needs the same tracking, and the records have to land in one place.
Read the hostname and keep the first one
On Velocity, the address the client sent is available from getVirtualHost() on the player's connection. Store it against the player's UUID the first time you see that player, and never overwrite it.
Keeping the first address matters more than it sounds. A player who first came from a creator may type the main address a week later, and a regular may save a creator's address and use it for months. If the latest address wins, credit drifts away from whoever brought the player in. Count two things and keep them apart:
- New players per address: players whose very first join came through it. This is the number that tells you what a source brought.
- Joins per address: every connection through it. Useful to see which addresses people saved, useless for judging a source.
Clean the value before you store it. Lowercase it, drop a trailing dot and any :port suffix, and keep joins by raw IP address and empty values in groups of their own instead of guessing where they came from.
You can write this yourself as a small Velocity plugin. Plan also records join addresses, and MCAnalytics does it in its Velocity connector, as described in first join hostname attribution.
Pitfalls that hide the source
Players type the main address anyway
Some viewers remember your server's name and type the main address, and friends pass on whatever address they use. Treat every source number as a floor, not the full count. Make the creator's address easy to use: on screen during the video, in the description, in a pinned comment, and said out loud. For purchases, a creator code at checkout is a second signal that does not depend on the address; see creator codes.
DDoS protection in front of the proxy
Services such as TCPShield or NeoProtect sit between the player and Velocity. They pass the connection on to your proxy, and they hand over the player's real IP with the PROXY protocol or through their own plugin. Velocity reads PROXY protocol headers when haproxy-protocol = true is set in the [advanced] section of velocity.toml, as its configuration docs describe.
Whether the typed hostname reaches Velocity intact depends on that layer and on how it is set up, so test it the same way: join through a new address and check what the proxy recorded. If your provider asks you to list your domains, add every tracking subdomain there too.
Bedrock players through Geyser
Bedrock players connect to Geyser, and Geyser connects on to your proxy. Geyser has a forward-hostname option in its remote section that forwards the address the Bedrock client used, meant for forced hosts on proxies, per the Geyser config docs. Check that it is on, or every Bedrock player arrives without a useful address.
Retired or reused addresses
Removing a DNS record breaks the saved server entry of every player who used it. Keep old tracking addresses pointing at the proxy after a deal ends, and make a new address for the next deal instead of reusing an old one, so two deals never mix.
Where MCAnalytics fits
Everything above works with any tool that records the hostname, including a plugin you write yourself. MCAnalytics does it in its Velocity connector: the first address is kept for the life of the player, joins with no address land in an explicit unattributed group, and every address is shown with its new players, 7-day return rate and Tebex revenue. New addresses show up on their own after their first join. See source tracking and subdomains and creator campaigns.