# Online vs Offline Mode The `online-mode` server property is the root of every forwarding decision. Understand it first; the four forwarding modes are all answers to the problem offline mode creates. ## What `online-mode` controls `online-mode=true` (the default in `server.properties`) means: **after** the login handshake, the server verifies the connecting player against Mojang's session servers. It does **not** by itself control whether the connection is encrypted. The login sequence (see [../05-login-encryption.md](../05-login-encryption.md) for the full packet flow) is, in both modes: 1. Client → `LoginStart` (username, and on 1.19+ a profile public key / on 1.20.2+ the profile UUID). 2. Server → `Encryption Request` (server-id string, server's RSA public key, verify token). 3. Client → `Encryption Response` (the AES shared secret + verify token, both RSA-encrypted with the server's public key). 4. Both sides switch to **AES/CFB8** encryption using the shared secret. Steps 2–4 — the encryption negotiation — **happen regardless of online/offline mode** whenever the vanilla server requests them. What online mode adds is one extra check between steps 3 and 4 conceptually: - **Online mode**: the server computes the *server-id hash* (SHA-1 over the server-id string + shared secret + server public key) and calls Mojang's session endpoint **`hasJoined`**: `https://sessionserver.mojang.com/session/minecraft/hasJoined?username=&serverId=`. Mojang confirms that this account really just authenticated with that server-id (the client side calls the matching `join` endpoint). The response carries the player's **real account UUID** and **properties** (the `textures` property — skin & cape, signed by Mojang). If `hasJoined` returns nothing, the login is rejected. - **Offline mode**: the server **skips the `hasJoined` call entirely**. No Mojang verification. The server trusts the username from `LoginStart` as-is. (Vanilla offline servers also skip the encryption step; the point is that *no identity proof* is required.) So the difference that matters for forwarding is: **offline mode does not call `hasJoined`, so it has no proof the username is real, and it gets no real UUID or skin from Mojang.** It must invent both. ## UUID derivation How the player's UUID is determined differs by mode: - **Online mode**: the UUID is the player's **real Mojang account UUID**, returned by the `hasJoined` response. It's a stable, account-bound, "version 4"-style identifier assigned by Mojang. - **Offline mode**: the server **derives** a UUID deterministically from the username. The algorithm is a **name-based (version 3 / MD5) UUID** over the bytes of the ASCII string `"OfflinePlayer:" + username`: ```java UUID offlineId = UUID.nameUUIDFromBytes( ("OfflinePlayer:" + name).getBytes(StandardCharsets.UTF_8)); ``` `java.util.UUID.nameUUIDFromBytes` produces an **RFC-4122 version 3** UUID — it MD5-hashes the input bytes and stamps the version/variant bits. The same username always yields the same offline UUID, on every server, forever. (Note: no namespace UUID is prepended — it's a plain MD5 of just those bytes, which is why this is "version-3-like" rather than a strictly RFC-compliant namespaced v3.) This is confirmed directly in BungeeCord's source — `InitialHandler.finish()`: > `offlineId = UUID.nameUUIDFromBytes( ( "OfflinePlayer:" + getName() ).getBytes( StandardCharsets.UTF_8 ) );` > > — `BungeeCord/proxy/src/main/java/net/md_5/bungee/connection/InitialHandler.java:560` and the [minecraft.wiki](https://minecraft.wiki/w/Universally_unique_identifier) describes it the same way: *"the unique identifier chosen for offline mode players is UUID version 3, generated from the MD5 hash of `OfflinePlayer:`."* The practical consequence: a premium player has **different** UUIDs online vs offline. This is exactly why forwarding must carry the *real* (online) UUID to the backend — otherwise the offline backend would assign the player a different identity than the one their account/inventory/permissions are keyed to. ## Security model > **An offline-mode backend with a reachable port is wide open.** Because an offline server does no `hasJoined` check, it accepts **whatever username the connecting party sends in `LoginStart`** and derives the UUID from it. There is no proof. Anyone who can reach the port can connect as `Notch`, as an admin, as any player — and the offline server will hand them that player's UUID, inventory, and op level. In a proxy setup the player's identity is established **at the proxy** (which runs online mode and does the real `hasJoined`). The backends are deliberately offline so the proxy can move players. That is the entire reason forwarding + hardening exist: 1. **Forwarding** gives the backend the player's *real* IP / UUID / properties instead of the bogus offline-derived ones — see [bungeecord-legacy.md](bungeecord-legacy.md) and [velocity-modern.md](velocity-modern.md). 2. **Hardening** stops an attacker from connecting *directly* to the offline backend and forging that same identity: - **Firewall** the backend so only the proxy's address can connect (the *only* defense for bare legacy / none). - **BungeeGuard** — a shared secret token the backend checks ([bungeeguard.md](bungeeguard.md)). - **Modern/Velocity** — an HMAC the backend verifies, so forged identities are cryptographically rejected ([velocity-modern.md](velocity-modern.md)). Bind your backends to a private interface and trust nothing that arrives without one of these proofs. --- **Sources** - `BungeeCord/proxy/src/main/java/net/md_5/bungee/connection/InitialHandler.java:560` — offline UUID = `nameUUIDFromBytes("OfflinePlayer:"+name)`; `:135` `onlineMode`; `:595` `isOnlineMode()` gating the auth call. - [minecraft.wiki — Universally unique identifier](https://minecraft.wiki/w/Universally_unique_identifier) — version-3 MD5 `OfflinePlayer:` derivation. - [minecraft.wiki — Java Edition protocol (login + encryption)](https://minecraft.wiki/w/Java_Edition_protocol) — Encryption Request/Response, server-id hash, `hasJoined`. - See also [../05-login-encryption.md](../05-login-encryption.md) for the encryption packet detail.