A server.toml option, [server] accept_older_clients (horizon-accept-older-clients), off by default. On, a login handshake stating 776 opens an adapter::ClientSession on the connection instead of being refused, and a 26.2 status ping is told its own protocol. The session follows the connection's states as Session does, mirrored, and renumbers packet ids both ways with the inverse of the generated 26.2 tables, in the blocking login path and in the io_pool play path. Off, nothing changes. Part of #10404.
16 KiB
Server reference
Everything horizon-server offers a host or a tool: its commands, every server.toml key and the control API. How to host: hosting.md; the design: server.md; stability and versions: stability.md.
Commands
horizon-server help prints:
horizon-server: host a Horizon (Minecraft Java Edition 26.3) server
Usage: horizon-server [--dir DIR] <command>
init Set up a server here: the EULA, the world, the port, who may join (asks, or takes flags:
--eula --world NAME --seed SEED --port N --whitelist/--no-whitelist --player NAME... --op NAME
--offline --yes)
run Run the server in the foreground; type console commands, Ctrl-C stops it cleanly. It
restarts the game if it crashes.
start Run the server in the background, or resume a stopped supervisor (output goes to logs/supervisor.log)
stop, restart
status [--json]
Up or not, TPS, MSPT, players, RAM and CPU
cmd COMMAND...
Run a console command on the running server and print what it answers
report [SECONDS]
Record a performance report for SECONDS (default 60): where the ticks' time goes,
entities, chunks, network, memory; saved in reports/ (JSON and text, no player data)
logs [-f] [-n N]
The last N lines of the log (default 50); -f follows it
console Attach to the running server: its log as it happens, TPS, MSPT, players and RAM at the
top, and a command line for console commands; Ctrl-D detaches
mcp An MCP server on stdin and stdout, for an AI agent to run and moderate the server
(players, kick, ban, whitelist, logs, events, backups, settings, restart...)
backup [NAME]
Zip the world and the settings into backups/ (a running server saves first and stays up)
backups [--json]
List the backups
restore BACKUP [--dimension ID] [--yes]
Put a backup's world in place (the current one is kept in backups/); a running
server restarts with it. --dimension (like minecraft:the_nether) brings back only
that dimension and leaves the rest of the world as it is
service install|uninstall|print [--system]
Start the server at boot or login: a systemd unit on Linux (--system: for every user,
with sudo), a launchd agent on macOS, a task at logon on Windows
panel A one-time login link for the web panel ([panel] in server.toml; 127.0.0.1:8130 by
default)
update [--check [--json]] [--version TAG] [--timeout SECONDS]
Install the newest release (checked against GitHub's SHA-256), back up the world and
restart on it; goes back by itself if the game isn't up within the timeout (300 s).
--archive FILE --version NAME installs an archive you have
rollback Back to the release used before the update
config [--json]
The settings in server.toml
config set KEY=VALUE... [--json]
Change settings (for example world.difficulty=hard server.max_players=10), checked first;
a running server takes the difficulty, default game mode and whitelist at once, the rest
at its next start
import FILE Write server.toml from a vanilla server.properties
import-resources DIR
Copy game files prepared elsewhere (by the launcher or horizon-prepare) into place, after
checking they are complete and for this Minecraft version; the old copy stays on failure
op NAME, deop NAME
whitelist on|off|list, whitelist add|remove NAME
Who may join (on the running server, or in the files when it is stopped)
players [--json]
Who is online, the operators, the whitelist and the bans
kick NAME [REASON], ban NAME [REASON], pardon NAME, ban-ip ADDRESS|NAME [REASON], pardon-ip ADDRESS
Moderate the running server, or edit ban files while it is stopped
--dir DIR The server folder (default: the current folder)
Settings live in server.toml; the server writes server.properties from it before every start.
server.toml
Horizon server settings (docs/server.md). The server writes them into server.properties before every start: edit this file, not that one. Unknown keys are refused. horizon-server init writes this file with these defaults; horizon-server config set SECTION.KEY=VALUE changes it.
[server]
| Key | Default | In server.properties |
What it does |
|---|---|---|---|
motd |
"A Minecraft Server" |
motd |
the line under the server's name in the server list |
port |
25565 |
server-port |
the port players connect to |
bind |
"" |
server-ip |
listen on every address, or only on this one (server-ip) |
max_players |
20 |
max-players |
how many players can be online at once |
online_mode |
true |
online-mode |
check players' Minecraft accounts (false: anyone joins under any name) |
whitelist |
true |
white-list |
only whitelisted players and operators can join |
enforce_whitelist |
false |
enforce-whitelist |
kick online players taken off the whitelist |
view_distance |
10 |
view-distance |
chunks sent around each player |
simulation_distance |
10 |
simulation-distance |
chunks around each player where mobs, crops and redstone tick |
max_view_distance |
0 |
horizon-max-view-distance |
past view_distance, saved chunks only, up to this (never generated; 0: off) |
admin_feedback_to_ops |
true |
horizon-admin-feedback-to-ops |
operators see each other's commands ([name: ...], vanilla); false: only the log |
accept_older_clients |
false |
horizon-accept-older-clients |
true: 26.2 clients join too, their packets translated (docs/hosting.md) |
[world]
| Key | Default | In server.properties |
What it does |
|---|---|---|---|
name |
"world" |
level-name |
the world folder |
seed |
"" |
level-seed |
empty: a random seed on the first start |
game_mode |
"survival" |
gamemode |
survival, creative, adventure or spectator |
difficulty |
"easy" |
difficulty |
peaceful, easy, normal or hard |
hardcore |
false |
hardcore |
one life: a player who dies becomes a spectator |
[properties]
Any other server.properties key, spelled as vanilla spells it, for example: spawn-protection = 16
[engine]
| Key | Default | In server.properties |
What it does |
|---|---|---|---|
path |
"" |
the game program; empty: the one installed beside horizon-server | |
resources |
"" |
the prepared game files; empty: resources/ here, else the usual places | |
restart_on_crash |
true |
start the game again when it crashes | |
low_memory |
false |
true: no transparent huge pages, ~60 MB less idle for ~1 % more tick time |
[backups]
| Key | Default | In server.properties |
What it does |
|---|---|---|---|
every |
"6h" |
automatic backups while the server runs: 30m, 6h, 1d...; "0" turns them off | |
keep |
12 |
automatic backups kept (backups made by hand are never deleted) |
[updates]
| Key | Default | In server.properties |
What it does |
|---|---|---|---|
keep |
1 |
other releases kept by updates, besides the one in use and the one rollback goes to |
[panel]
| Key | Default | In server.properties |
What it does |
|---|---|---|---|
bind |
"127.0.0.1:8130" |
the web panel: this computer only, or a tailnet address (100.x); "" turns it off | |
allow_public |
false |
true lets bind be any address; never put the panel on the open internet |
[api]
| Key | Default | In server.properties |
What it does |
|---|---|---|---|
bind |
"" |
the control API: "" a free port on 127.0.0.1; or a fixed one, like 127.0.0.1:8131 or a tailnet address | |
token |
"" |
its token (16 characters or more); "" a new random one at every start | |
allow_public |
false |
true lets bind be any address; the token is then all that guards the server |
[transfer]
A proxy handing players over between servers (docs/server.md). Not vanilla's accepts-transfers (the transfer packet).
| Key | Default | In server.properties |
What it does |
|---|---|---|---|
enabled |
false |
true: take a proxy's player-* requests; needs Velocity forwarding (forwarding.secret) | |
expect_seconds |
10 |
how long a player the proxy announced is waited for, when it names no time | |
max_expect_seconds |
60 |
the longest the proxy may ask for | |
max_expected |
64 |
players waited for at once | |
max_freeze_seconds |
30 |
the longest a player stays frozen while it is handed over | |
max_hold_seconds |
60 |
the longest a player arriving from another server is held before it is dropped |
[discord]
Post the chat, joins, leaves and deaths to a Discord channel, drawn like the in-game chat (docs/hosting.md)
| Key | Default | In server.properties |
What it does |
|---|---|---|---|
webhook |
"" |
the channel's webhook URL (a secret: anyone with it can post there); "" off | |
events |
["chat", "join", "leave", "death"] |
what is posted; "kick" too if you like | |
enabled |
true |
false pauses it and keeps the webhook |
Control API (/api/v1)
HTTP and JSON on 127.0.0.1. A running server writes its address and a random token to .horizon/api.json, readable only by its own user; every request sends Authorization: Bearer <token> and a Host that is that address. Errors answer {"error": "..."} with 400, 401, 403, 404, 405 or 409. The web panel uses the same routes with its session.
| Method | Path | Takes | Answers | What it does |
|---|---|---|---|---|
GET |
/api/v1/status |
nothing | phase, version, pid, uptime_seconds, engine_pid, engine_uptime_seconds, crashes, last_crash, sample (TPS, MSPT, players, RAM, CPU), update |
How the server does |
GET |
/api/v1/metrics |
?since=T (seconds since 1970) |
{"samples": [...]}: one a second, the last 10 minutes |
The samples taken after T, for graphs |
GET |
/api/v1/logs |
?since=N |
{"next": N, "lines": [...]} |
The log lines after the Nth (the last 2000 are kept) |
GET |
/api/v1/events |
?since=N (or Last-Event-ID), ?types=join,leave,kick,chat,chat_filtered,death,log,phase (all by default) |
with Accept: text/event-stream, server-sent events as they happen (id, event: the type, data: JSON); otherwise {"next": N, "events": [...]} |
Log lines, the supervisor's phase, and the game's joins, leaves, kicks, chat, messages the chat filter refused (chat_filtered, without their text) and deaths (the last 2000 kept) |
POST |
/api/v1/command |
{"line": "<one command>"} |
the lines the command logged | Run a console command on the running game |
POST |
/api/v1/stop |
nothing | {"ok": true} |
Stop the game and the supervisor |
POST |
/api/v1/start |
nothing | {"ok": true} |
Start the game while keeping the supervisor and API running |
POST |
/api/v1/restart |
nothing | {"ok": true} |
Restart the game |
GET |
/api/v1/backups |
nothing | {"backups": [{"name", "bytes"}]} |
List the backups |
POST |
/api/v1/backups |
{"name": "..."} (optional) |
{"name": "<file>"} |
Back up now (a running game saves first and stays up) |
DELETE |
/api/v1/backups |
?name=<file> (the full name backups lists) |
{"name": "<file>"} |
Delete a backup |
GET |
/api/v1/backups/download |
?name=<file> |
the zip | Download a backup |
GET |
/api/v1/crash-reports |
nothing | {"reports": [{"name", "bytes"}]}, newest first (the .txt files in crash-reports/) |
List the game's crash reports |
GET |
/api/v1/crash-reports/read |
?name=<file> (a name crash-reports lists) |
the report's text (text/plain) |
Read one crash report |
POST |
/api/v1/restore |
{"name": "<backup>", "dimension": "minecraft:the_nether"} (dimension optional) |
{"name": "<backup>", "dimension": "<id>" or null} |
Restart the game with that backup's world, or only one dimension of it (what it replaces is kept in backups/) |
POST |
/api/v1/update |
{"tag": "...", "timeout": seconds} (both optional) |
{"ok": true}; status says how it went (update) |
Restart on that installed release, or download the newest first; goes back if the game isn't up within the timeout |
GET |
/api/v1/metrics/prometheus |
nothing | the latest sample as Prometheus text: horizon_up, horizon_tps, horizon_mspt{stat}, horizon_players_online, horizon_memory_rss_bytes, horizon_loaded_chunks{dimension} and horizon_entities{dimension} (players and block entities apart)... |
Metrics for a monitoring tool to scrape |
GET |
/api/v1/players |
nothing | online (name, uuid, address, ping_ms, dimension, position, op_level), max_players, ops, whitelist (enabled, players), bans, ip_bans (with reason, source, created, expires) |
Who is online, and vanilla's lists |
GET |
/api/v1/mods |
nothing | engine (found, target, base, horizon, hooks) and native (id, version, sha256), from the native-mods.json the launcher wrote beside the game program |
The native mods the server's game program was built with |
POST |
/api/v1/players/kick |
{"name", "reason"} (reason optional) |
{"changed": bool, "output": [...]}: whether the list (or who is online) changed, and the console's lines |
Kick an online player |
POST |
/api/v1/players/ban |
{"name", "reason"} (reason optional) |
{"changed": bool, "output": [...]}: whether the list (or who is online) changed, and the console's lines |
Ban a player (kicked if online) |
POST |
/api/v1/players/pardon |
{"name"} |
{"changed": bool, "output": [...]}: whether the list (or who is online) changed, and the console's lines |
Lift a player's ban |
POST |
/api/v1/players/ban-ip |
{"ip", "reason"}, or {"name"} of an online player |
{"changed": bool, "output": [...]}: whether the list (or who is online) changed, and the console's lines |
Ban an address (its players are kicked) |
POST |
/api/v1/players/pardon-ip |
{"ip"} |
{"changed": bool, "output": [...]}: whether the list (or who is online) changed, and the console's lines |
Lift an address's ban |
POST |
/api/v1/players/op |
{"name"} |
{"changed": bool, "output": [...]}: whether the list (or who is online) changed, and the console's lines |
Make a player an operator (op-permission-level) |
POST |
/api/v1/players/deop |
{"name"} |
{"changed": bool, "output": [...]}: whether the list (or who is online) changed, and the console's lines |
Make a player no longer an operator |
POST |
/api/v1/whitelist |
{"enabled": bool} |
{"changed": bool, "output": [...]}: whether the list (or who is online) changed, and the console's lines |
Turn the whitelist on or off |
POST |
/api/v1/whitelist/add |
{"name"} |
{"changed": bool, "output": [...]}: whether the list (or who is online) changed, and the console's lines |
Whitelist a player |
POST |
/api/v1/whitelist/remove |
{"name"} |
{"changed": bool, "output": [...]}: whether the list (or who is online) changed, and the console's lines |
Take a player off the whitelist (kicked if enforce-whitelist) |
GET |
/api/v1/config |
nothing | {"config": {...}}: server.toml's settings by section (api.token hidden) |
The settings |
PATCH |
/api/v1/config |
{"changes": {"world.difficulty": "hard", ...}} |
saved, applied (taken by the running game at once), restart (from the next start), running |
Change settings as config set does; not engine.*, panel.*, api.*, nor RCON, query or management keys |
POST |
/api/v1/panel/login |
nothing | {"url": "..."} |
A one-time login link for the web panel (with the token only, not from the panel) |