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 | |
stop_wait_seconds | 10 | a stop with players on waits this long for the proxy to move them out (0 to 60; 0: no wait) |
[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) |
Edit this page on GitHub · Spotted something wrong? Open an issue or ask on Discord.