Offline Mode
Offline mode skips Mojang authentication entirely. The proxy does not verify the player's identity. It still parses every packet, so plugins, server switching, and limbo handlers all work the same as in client-only mode.
When to use it
Use offline mode when:
- Your server allows cracked (non-premium) clients
- You're developing locally and don't want to deal with Mojang auth
- You want packet inspection and server switching without identity verification
- You handle authentication yourself through a plugin (AuthMe, etc.)
Configuration
A minimal server config file:
domains = ["mc.example.com"]
addresses = ["192.168.1.10:25565"]
proxy_mode = "offline"2
3
With Docker labels:
labels:
infrarust.domains: "mc.example.com"
infrarust.proxy_mode: "offline"2
3
Full example
This uses every option that applies to offline mode:
name = "lobby"
network = "main"
domains = ["play.mc.example.com", "*.mc.example.com"]
addresses = ["10.0.1.10:25565", "10.0.1.11:25565"]
proxy_mode = "offline"
send_proxy_protocol = false
max_players = 200
disconnect_message = "Server is offline. Try again later."
limbo_handlers = ["motd", "tablist"]
[timeouts]
connect = "3s"
read = "30s"
write = "30s"
[motd.online]
text = "§6Lobby — Online"2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Config options
| Option | Type | Default | Description |
|---|---|---|---|
domains | string array | [] | Domains that route to this server. Supports wildcards (*.mc.example.com). Can be empty if the server is only reachable via server switching within a network. |
addresses | string array | required | Backend addresses in host:port format. |
proxy_mode | string | "passthrough" | Set to "offline". |
network | string | none | Network name. Servers in the same network can switch players between each other. |
send_proxy_protocol | bool | false | Send PROXY protocol header when connecting to the backend. |
max_players | integer | 0 | Maximum players on this server. 0 means unlimited. |
disconnect_message | string | "Server is currently unreachable..." | Message sent to the player when the backend is down. |
limbo_handlers | string array | [] | Plugin IDs for limbo handler chain, executed in order. |
timeouts.connect | duration | "5s" | How long to wait when connecting to the backend. |
timeouts.read | duration | "30s" | Read timeout on the backend connection. During the login, the longest silence allowed between two backend packets while nothing waits on the client, and the time the client has to acknowledge the login. See Login time limits. |
timeouts.write | duration | "30s" | Write timeout on the backend connection. |
TIP
Duration values use human-readable format: "5s", "30s", "2m", "1h".
How it works
- The proxy reads the client's handshake and login start packets.
- It fires a
PreLoginEvent, giving plugins a chance to deny the connection. - It fires a
GameProfileRequestEvent, where plugins may rewrite the player's profile, then checks bans against the final profile. No RSA key exchange, no encryption, no Mojang session verification. - It fires
PermissionsSetupEventandLoginEvent; a plugin can still refuse the player here. - It registers the player and fires
PostLoginEvent. - It connects to the backend. How the login goes on depends on the server:
- No forwarding and an untouched profile: the proxy forwards the client's raw handshake and login packets, and the backend completes the login (its
SetCompressionandLoginSuccessgo straight to the client). - Forwarding (
bungee_cord,bungee_guard,velocity) or a profile rewritten inGameProfileRequestEvent: the proxy sends its own handshake, carrying the forwarding data, and its own login start. It relays the backend's login plugin messages and login cookie requests to the client and the answers back, with their message ids unchanged, and answersvelocity:player_infoitself. Once the backend accepts the login, the proxy sendsSetCompression(seecompression_threshold) andLoginSuccessto the client.
- No forwarding and an untouched profile: the proxy forwards the client's raw handshake and login packets, and the backend completes the login (its
- The proxy enters the session loop, parsing and relaying packets in both directions.
If the player ends up in limbo instead (because limbo handlers are configured or a plugin redirects there), the proxy sends SetCompression and LoginSuccess itself, and a backend reached later through a server switch gets "not understood" answers to its login plugin messages. When the backend refuses the login, its reason reaches the client as a login disconnect.
Login time limits
On both paths the proxy bounds the login:
- The backend gets
timeouts.read(30 seconds by default) between two of its login packets. Packets from the client do not extend it. - While a login plugin message relayed to the client waits for its answer, that deadline is suspended: a mod may show the player a prompt first. The client gets at most 5 minutes, counted from the first request it has not answered.
- Once
LoginSuccessreaches the client, the client hastimeouts.readto acknowledge it (1.20.2 and later).
Past any of these limits the player is disconnected. A client answer to a request the client was never sent, such as one a plugin answered itself, is dropped instead of reaching the backend.
Connection lifecycle
Client ──TCP──▶ Infrarust ──TCP──▶ Backend
│
reads handshake + login start
│
PreLoginEvent (plugins can deny)
GameProfileRequestEvent, ban check
PermissionsSetupEvent, LoginEvent (plugins can deny)
PostLoginEvent
│
┌─────────┴──────────────────────────┐
no forwarding, forwarding or
untouched profile rewritten profile
│ │
raw handshake + login own handshake (+ forwarding)
──▶ Backend + login start ──▶ Backend
│ │
Backend's SetCompression login plugin messages
+ LoginSuccess ──▶ Client Backend ◀──▶ Client
│ │
│ Backend ──▶ LoginSuccess (consumed)
│ SetCompression + LoginSuccess ──▶ Client
└─────────┬──────────────────────────┘
│
session loop ─────────▶ parsed packets flow
both directions2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
UUID generation
Since there's no Mojang session, the proxy derives the player's UUID from the name, the same way a vanilla server in offline mode does: the same name always gets the same UUID, whatever the client claims. Set [auth] offline_uuid = "client" to use the UUID the client sends in its login start packet instead (1.19.1 and later, falling back to the name-based UUID). A plugin can also set any UUID in GameProfileRequestEvent.
Differences from client-only
| Client-only | Offline | |
|---|---|---|
| Mojang authentication | Yes | No |
| Client-side encryption | Yes (AES/CFB8) | No |
LoginSuccess sent by proxy | Yes, after the backend accepted the login | Only with forwarding, a rewritten profile or a limbo |
| Packet inspection | Yes | Yes |
| Server switching | Yes | Yes |
| Limbo handlers | Yes | Yes |
| Cracked clients | No | Yes |
Both modes are intercepted: the proxy parses every packet, sessions are active, and plugins can inject packets or move players between servers.
Server networks
Offline mode supports networks, just like client-only. Servers in the same network can switch players between each other.
name = "hub"
network = "main"
domains = ["play.mc.example.com"]
addresses = ["192.168.1.10:25565"]
proxy_mode = "offline"2
3
4
5
name = "survival"
network = "main"
addresses = ["192.168.1.11:25565"]
proxy_mode = "offline"2
3
4
You can mix offline and client-only servers within the same network. The survival server above has no domains, so players reach it only through a server switch from within the network.
INFO
Only intercepted modes (client_only, offline) can belong to a network. Forwarding modes (passthrough, zero_copy, server_only) are rejected during config validation if they specify a network.
Security considerations
No identity verification
Without Mojang authentication, anyone can connect with any username. A malicious player can impersonate an admin or any other player by using their username. Do not use offline mode on public-facing servers without additional authentication.
If your server is public and you need cracked client support, put an authentication plugin on the backend (AuthMe, nLogin, etc.) that verifies players through a password or other mechanism before granting them access.
If you only need offline mode for local development, bind the proxy to 127.0.0.1 in the global config so it's not reachable from the network.
No traffic between the client and proxy is encrypted in offline mode. On a local network or behind a VPN this is fine. Over the public internet, anyone between the client and proxy can read the traffic. For public servers that need encryption, use client-only mode instead.
Compared to other modes
If offline mode doesn't fit your needs:
- Need authentication? Use client-only. Same packet inspection and server switching, with Mojang identity verification.
- Don't need server switching or plugins? Use passthrough. Lower overhead, works with every Minecraft version.
- Need lower CPU on Linux without server switching? Use zero-copy.