mcstatus-httpd shows any information of a Minecraft Server via HTTP-Server as JSON. It uses mcstatus.
Features:
- Minecraft Java Edition and/or Bedrock Edition (a server that is not configured is omitted from the output)
- Java: full player list and plugins via Query, automatic fallback to the normal status ping if Query is disabled
- Bedrock: NetherNet (default since Bedrock 1.26.50) and RakNet (legacy), both are tried at the same time by default
- Both servers are queried in parallel, results are cached (protects the Minecraft server)
- Optional player avatars (Java, head or body), loaded via mcstatus-httpd with cache and provider fallback
- Optional sub path (
HTTP_PREFIX), e.g. for a reverse proxy - Optional HTTPS
- Built-in health check command
--healthcheckfor docker-compose (also works with the distroless image) - Configuration via environment variables and/or command line arguments
This is (currently) written for my benefit only.
You can use it, but I won't give support if it doesn't work for you. (I'm also not a Python expert and it was written with the help of AI!).
But you are welcome to work on it. ;-)
I use Minecraft Server with Docker:
Examples for docker-compose (minecraft.yml, minecraft-bedrock.yml) you can find here: click
- Python >= 3.13
- mcstatus >= 14.2.0
- Minecraft Java: Query should be enabled (
enable-query=trueinserver.properties).
Without Query the status ping is used as fallback, which only provides a player sample (max. ~12 names) and no plugins and map name. - Minecraft Bedrock with NetherNet: the server port must be reachable via TCP (status via
GET /v1/join).
With RakNet (Bedrock server settingtransport=raknet): the server port must be reachable via UDP.
Every setting can be set as environment variable or as command line argument (arguments take precedence). See python3 mcstatus-httpd.py --help.
| Environment variable | Argument | Default | Description |
|---|---|---|---|
MINECRAFT_JAVA_SERVER |
--java-server |
(disabled) | Java server address |
MINECRAFT_JAVA_SERVER_PORT |
--java-port |
25565 |
Java game port (TCP) |
MINECRAFT_JAVA_SERVER_PORT_QUERY |
--java-query-port |
same as game port | Java query port (UDP) |
MINECRAFT_JAVA_SERVER_OVERWRITE |
--java-overwrite |
same as server | Hostname shown in the output |
MINECRAFT_BEDROCK_SERVER |
--bedrock-server |
(disabled) | Bedrock server address |
MINECRAFT_BEDROCK_SERVER_PORT |
--bedrock-port |
19132 |
Bedrock game port (NetherNet: TCP, RakNet: UDP) |
MINECRAFT_BEDROCK_SERVER_OVERWRITE |
--bedrock-overwrite |
same as server | Hostname shown in the output |
MINECRAFT_BEDROCK_TRANSPORT |
--bedrock-transport |
auto |
nethernet (TCP, default since Bedrock 1.26.50), raknet (UDP, legacy) or auto (both at the same time) |
MINECRAFT_TIMEOUT |
--timeout |
3 |
Seconds until a server counts as offline |
CACHE_TTL |
--cache-ttl |
10 |
Seconds a result is reused, 0 = query on every request |
HTTP_HOST |
--http-host |
0.0.0.0 |
Listen address, 0.0.0.0 = IPv4 only, :: = IPv4 + IPv6 |
HTTP_PORT |
--http-port |
8080 |
Listen port |
HTTP_PREFIX |
--http-prefix |
(none) | Sub path, e.g. mcstatus → /mcstatus |
HTTP_CORS_ORIGIN |
--http-cors-origin |
* (all) |
Websites allowed to load the status (CORS), e.g. https://forum.example.com,https://example.com |
HTTP_TRUSTED_PROXIES |
--http-trusted-proxies |
(none) | Reverse proxies (IPs/networks, e.g. 172.18.0.0/16) whose X-Forwarded-For/X-Real-IP header is used for the client IP in the log |
HTTPD_SSL_ENABLE |
--ssl / --no-ssl |
0 |
Enable HTTPS (1/true/yes/on) |
SSL_CERTFILE |
--ssl-certfile |
ssl.crt |
TLS certificate file |
SSL_KEYFILE |
--ssl-keyfile |
ssl.key |
TLS private key file |
AVATAR_ENABLE |
--avatar / --no-avatar |
0 |
Player avatars (Java), see Player avatars |
AVATAR_TYPE |
--avatar-type |
head |
Default type: head or body |
AVATAR_IMAGE_SIZE |
--avatar-image-size |
64 |
Default image width in pixels requested from the providers (8-300), body images are twice as high |
AVATAR_ALLOWED_TYPES |
--avatar-allowed-types |
head,body |
Types that can be requested with ?type= (the default type is always allowed) |
AVATAR_ALLOWED_SIZES |
--avatar-allowed-sizes |
16,32,48,64,96,128,192,256,300 |
Sizes that can be requested with ?size= (8-300, the default size is always allowed) |
AVATAR_PROVIDERS |
--avatar-providers |
mc-heads,minotar |
Avatar providers in order of preference (fallback), built-in names or own URL templates |
AVATAR_CACHE_TTL |
--avatar-cache-ttl |
3600 |
Seconds an avatar is used before it is loaded again (the old one is kept if loading fails) |
AVATAR_CACHE_TYPE |
--avatar-cache-type |
memory |
memory (lost on restart) or file |
AVATAR_CACHE_DIR |
--avatar-cache-dir |
/tmp/mcstatus-httpd/avatars |
Directory of the file cache |
AVATAR_CACHE_MAX_ENTRIES |
--avatar-cache-max-entries |
500 |
Maximum number of cached avatars (every type and size counts), 0 = unlimited |
AVATAR_TLS_VERIFY |
--[no-]avatar-tls-verify |
1 |
0 = accept self-signed HTTPS certificates of own URL templates (built-in providers are always verified) |
AVATAR_ONLINE_ONLY |
--[no-]avatar-online-only |
1 |
Only serve avatars of online players, 0 = any valid player name (see below) |
LOGLEVEL |
--loglevel |
INFO |
DEBUG, INFO, WARNING, ERROR, CRITICAL |
At least one server (Java or Bedrock) must be configured.
With HTTPS enabled, HTTP_PORT only serves HTTPS (no plain HTTP), e.g. use HTTP_PORT=8443.
If HTTPS is enabled but the certificate files are missing, the service does not start.
IPv4 + IPv6 behind a reverse proxy (e.g. Docker network with --ipv6):
HTTP_HOST: "::"
HTTP_TRUSTED_PROXIES: "172.18.0.0/16, 10.0.0.5, fd00:dead:beef::/48, ::1"HTTP_TRUSTED_PROXIES accepts any number of IPv4/IPv6 networks and single addresses (comma separated).
Only for requests from these proxies the client IP is taken from X-Forwarded-For/X-Real-IP (shown in the log with LOGLEVEL=DEBUG).
| Without prefix | With HTTP_PREFIX=mcstatus |
Description |
|---|---|---|
/ |
/mcstatus |
Minecraft server status (JSON) |
/healthcheck |
/mcstatus/healthcheck |
Health check of this service (JSON) |
/healthcheck |
Health check, always available without prefix | |
/avatar/<name> |
/mcstatus/avatar/<name> |
Avatar of a Java player who is online (PNG, any name with AVATAR_ONLINE_ONLY=0), optional ?type=body&size=128, only with AVATAR_ENABLE |
GET and HEAD are supported, a trailing slash and query strings (e.g. ?_=123) are ignored (except type and size of the avatar endpoint).
The status endpoint always answers with HTTP 200, an unreachable server is shown with "online": false.
With AVATAR_ENABLE=1 the website examples show avatars instead of the names of Java players (Bedrock provides no player names).
The name is shown as tooltip on mouse over and on tap.
- The avatars are loaded by mcstatus-httpd and cached, visitors only talk to your server. The avatar providers never see the IP addresses of your visitors.
- Only players who are currently online are served, other names return 404.
WithAVATAR_ONLINE_ONLY=0any valid Java player name is served, e.g. as profile picture in your forum. Be aware that anybody can then use your service as avatar proxy: random names can push your players out of the cache, delay avatar downloads and cause rate limits at the providers. AVATAR_TYPE: default type,head(e.g. 64x64 px) orbody(e.g. 64x128 px, front view).AVATAR_IMAGE_SIZE: default image width loaded from the providers ({size}in URL templates). The website examples show the avatars in their own size (AVATAR_SIZEin the HTML) and scale them sharp (pixel art), so 64 is enough for most pages. Own URL templates without{size}always deliver their own size.- Type and size can be chosen per request, other values return 400:
Allowed are the values of
/avatar/Notch -> AVATAR_TYPE, AVATAR_IMAGE_SIZE /avatar/Notch.png?type=body&size=128 -> body, 128x256 pxAVATAR_ALLOWED_TYPESandAVATAR_ALLOWED_SIZES(a fixed list, so the number of cached variants stays small). AVATAR_PROVIDERS: providers in order of preference. If a provider fails (timeout, error, rate limit), the next one is used and the failing one is paused for a while (rate limits: as long as the provider asks for viaRetry-After).- Built-in:
mc-heads(mc-heads.net),minotar(minotar.net), both deliver all types and sizes. - Own URL templates with
{name}and optional{size}, e.g. of a server plugin (must deliver PNG). A template is used for the default type, or with a prefix likehead=orbody=for this type:If the plugin uses HTTPS with a self-signed certificate, setAVATAR_PROVIDERS: "head=http://minecraft:8100/tiles/faces/32x32/{name}.png, mc-heads, minotar"
AVATAR_TLS_VERIFY: 0(only for own URL templates, preferhttp://inside a Docker network).
- Built-in:
- Every Java account has a skin (the default skins Steve, Alex etc. if none was chosen). Names without Mojang account (offline-mode servers, Geyser/Floodgate players like
.Name,Anonymous Playerof hidden player lists):mc-headsdelivers the default head (Steve),minotaranswers 404 and the next provider is tried.- Names that are no valid Java names (e.g.
.Name) are not requested at all. - If no provider has an avatar, the website shows the name as text, so every player is listed.
- Cache:
- Avatars are loaded when they are requested (not on status requests) and used for
AVATAR_CACHE_TTLseconds. After that the next request loads it again from the providers. If none of them delivers it, the old avatar is kept and used (next attempt after 5 minutes). AVATAR_CACHE_TYPE=memory(default): in memory, lost on restart.AVATAR_CACHE_TYPE=file: PNG files inAVATAR_CACHE_DIR(<type>/<size>/<name>.png), kept across restarts. Without a volume the files are kept ondocker restart, but lost when the container is recreated (e.g. image update). To keep them, mount a volume:A named volume gets the right owner automatically. A bind mount (host directory) must be writable for the user of the container (distroless: UIDenvironment: AVATAR_CACHE_TYPE: file volumes: - mcstatus-avatars:/tmp/mcstatus-httpd/avatars
65532, alpine:nobody= UID65534). Withread_only: trueuse a volume ortmpfsfor the directory.AVATAR_CACHE_MAX_ENTRIES: the least recently used avatars are removed above the limit (every type and size counts).0= unlimited, recommended only withAVATAR_ONLINE_ONLY=1: with any name allowed, random names (mc-heads delivers the default skin for every name) can fill the cache.
- Avatars are loaded when they are requested (not on status requests) and used for
- examples/index.html: status page for any website (same box code as the WoltLab box, incl. the needed CSS)
- examples/wsc-box.html: status box for WoltLab Suite (ACP → Appearance → Boxes → box type "HTML" *), shows status, players, version, MOTD etc. Remove the lines you do not need.
Tip: Serve mcstatus-httpd on the same domain as your website via reverse proxy and HTTP_PREFIX (e.g. https://example.com/mcstatus).
A website served via HTTPS cannot load data from a plain http:// URL.
On a different domain this works too (CORS), by default every website is allowed, see HTTP_CORS_ORIGIN to restrict it.
Settings at the top of the script: MCSTATUS_URL, REFRESH_SECONDS, DATE_FORMAT ('' = page/browser language, e.g. de-DE or iso), SHOW_AVATARS, AVATAR_TYPE ('' = default of mcstatus-httpd, head or body), AVATAR_SIZE (height of a head in px, default 20) and TEXT.
with docker-compose (see docker-compose.yml):
services:
mcstatus-httpd:
image: ghcr.io/tob1as/mcstatus-httpd:latest
container_name: mcstatus-httpd
restart: unless-stopped
ports:
- 8080:8080/tcp
environment:
#TZ: Europe/Berlin
# Demo servers of https://mcstatus.io - replace with your own servers.
MINECRAFT_JAVA_SERVER: demo.mcstatus.io
MINECRAFT_JAVA_SERVER_PORT: 25565
#MINECRAFT_JAVA_SERVER_PORT_QUERY: 25565 # set, if UDP (query) is not equal to TCP port
#MINECRAFT_JAVA_SERVER_OVERWRITE: minecraft.example.com
MINECRAFT_BEDROCK_SERVER: demo.mcstatus.io
MINECRAFT_BEDROCK_SERVER_PORT: 19132
#MINECRAFT_BEDROCK_SERVER_OVERWRITE: minecraft-bedrock.example.com
#MINECRAFT_BEDROCK_TRANSPORT: auto # nethernet, raknet or auto
#HTTP_PREFIX: mcstatus
#AVATAR_ENABLE: 1 # player avatars (Java), see "Player avatars"
healthcheck:
test: ["CMD", "/venv/bin/python3", "/app/mcstatus-httpd.py", "--healthcheck"]
interval: 60s
timeout: 5s
start_period: 10s
retries: 3The health check uses python3 mcstatus-httpd.py --healthcheck (available since version 2), so no wget/curl is needed (works with the distroless image).
For the alpine image use /usr/local/bin/python3 instead of /venv/bin/python3.
local:
- install:
pip3 install -r requirements.txt - set variables/env or use arguments
- start:
python3 -u ./mcstatus-httpd.py --java-server demo.mcstatus.io --bedrock-server demo.mcstatus.io
docker build --target=production --build-arg VERSION="$(date -u +'%Y%m%d-%H%M%S').$(git rev-parse --short HEAD)" -t ghcr.io/tob1as/mcstatus-httpd:latest -f Dockerfile .The build argument VERSION is shown by --version, /healthcheck and in the logs (env MCSTATUS_HTTPD_VERSION in the image), without it the version is dev.
The GitHub workflow builds the Docker image (ghcr.io/tob1as/mcstatus-httpd):
| Trigger | Version | Image tags |
|---|---|---|
Push of a version tag, e.g. v2.0.0 |
2.0.0 |
2.0.0, 2.0, 2, latest, <build date>.<commit hash>, <commit hash> |
| Manual start in the GitHub GUI or schedule | <build date>.<commit hash> |
dev, <build date>.<commit hash>, <commit hash> |
A version tag additionally creates a GitHub release with mcstatus-httpd-<version>.zip and .tar.gz (script, requirements.txt, README.md, LICENSE).
Pre-releases like v2.0.0-rc1 only get the tag 2.0.0-rc1 and are marked as pre-release.
git tag v2.0.0
git push origin v2.0.0URL: http://localhost:8080
{
"date": "2026-01-01T12:00:00+00:00",
"java": {
"hostname": "minecraft.example.com",
"port": 25565,
"online": true,
"software": {
"version": "1.21.4",
"brand": "Paper on 1.21.4",
"plugins": ["LuckPerms 5.4.141", "EssentialsX 2.20.1"]
},
"players": {
"online": 3,
"max": 20,
"list": ["Player1", "Player2", "Player3"]
},
"map": "world",
"motd": "A Minecraft Server"
},
"bedrock": {
"hostname": "minecraft-bedrock.example.com",
"port": 19132,
"online": true,
"software": {
"version": "1.21.50",
"brand": "MCPE",
"protocol": 766
},
"players": {
"online": 0,
"max": 20
},
"map": "Bedrock level",
"motd": "Minecraft Bedrock Server",
"gamemode": "Survival"
}
}dateis the time of the query (UTC, ISO 8601). With caching, multiple requests can return the same value.motdis plain text (formatting codes removed).- Java without Query (status fallback):
brandandmaparenull,pluginsis empty andlistonly contains the player sample. - Bedrock via NetherNet:
brandisnull(not provided by NetherNet),motdis the server name.
Unreachable server:
{
"date": "2026-01-01T12:00:00+00:00",
"java": {
"hostname": "minecraft.example.com",
"port": 25565,
"online": false,
"error": "Server unreachable"
}
}Health check (/healthcheck):
{"status": "ok", "timestamp": "2026-01-01T12:00:00+00:00", "version": "20260101120000.a1b2c3d4"}dateis now ISO 8601 in UTC (before: local timeYYYY-MM-DD HH:MM:SS)pluginsandplayers.listare JSON arrays (before: comma separated string)motdis plain text (before: raw text with§formatting codes)- New field
online, unreachable servers no longer break the response /healthcheckreturns JSON (before: emptytext/plain), unknown paths return 404- Bedrock: support for NetherNet (
MINECRAFT_BEDROCK_TRANSPORT),brandisnullvia NetherNet - New: optional player avatars (
AVATAR_ENABLE)