A self-hosted Minecraft AFK bot system that keeps multiple accounts logged in to a server in parallel, each as its own isolated mineflayer instance, controllable from a modern web dashboard.
The original BennettSchwartz/minecraft-afkbot only supported switching
between stored credentials — one bot at a time. Axiom fixes that: every
added account runs its own bot concurrently, with per-account configuration,
behaviors, and chat streams.
Defaults to Minecraft Java 1.8.9 (overridable per account).
- True multi-account — N accounts, N concurrent mineflayer bots
- Microsoft device-code auth (premium accounts) and offline mode (cracked/LAN) — both supported per account
- Encrypted token storage — Microsoft tokens encrypted at rest in Postgres via AES-256-GCM, never logged
- Start / stop bots individually from the dashboard, including a
reconnect-suppression
stoppedstate so the bot doesn't auto-reconnect - Per-account anti-AFK behaviors
- Wiggle — jittered look/sneak actions
- Chat ping — fixed-interval chat messages rotating through a list
- Login commands — sequenced
/login,/warp, etc. with delays
- Templated commands — use
{{ordinal}},{{username}},{{label}}in any command/message. E.g. set the default login command to/f warp cac{{ordinal}}and account #1 runs/f warp cac1, account #2 runs/f warp cac2, etc. - Real-time UI — WebSocket pub/sub, no polling. Live status pills, chat streams, lifecycle events
- Dark mode, mobile-responsive, single shared dashboard token
- Exponential reconnect with jitter, capped at 5 min, with a hard retry limit so dead accounts don't loop forever
You need: Docker, Docker Compose, and 30 seconds.
git clone <this repo>
cd Axiom
# Create the env file
cp .env.example .env
# Generate two random 32-byte hex secrets
openssl rand -hex 32 # → paste as DASHBOARD_TOKEN
openssl rand -hex 32 # → paste as TOKEN_ENCRYPTION_KEY
# Set POSTGRES_PASSWORD to anything you want
# Bring it up
docker compose up -dOpen http://localhost:5005, enter your DASHBOARD_TOKEN, and add your
first account.
The dashboard container serves plain HTTP on HOST_PORT (default 5005).
TLS is terminated by your host's nginx using a Cloudflare Origin
Certificate. Traffic path:
browser ─HTTPS(CF edge)─▶ Cloudflare ─HTTPS(origin cert)─▶ host nginx :443 ─HTTP─▶ container :5005 ─▶ backend
This is the right shape when the host already runs nginx (and owns :443).
A ready-to-install site config lives at
deploy/nginx-host-alts.conf.
1. Create the Origin Certificate
Cloudflare dashboard for shaivilpatel.me:
SSL/TLS → Origin Server → Create Certificate (default RSA, hostnames
alts.shaivilpatel.me or *.shaivilpatel.me). Save the two PEM blocks on
the host:
sudo install -d -m 700 /etc/ssl/cloudflare
sudo nano /etc/ssl/cloudflare/alts.pem # the Origin Certificate
sudo nano /etc/ssl/cloudflare/alts.key # the Private Key
sudo chmod 600 /etc/ssl/cloudflare/alts.key2. Set Cloudflare SSL/TLS mode to Full (strict)
SSL/TLS → Overview → Full (strict) — Cloudflare validates the origin cert
on the hop to your server.
3. DNS + port forwarding
- An
A/AAAArecord foralts.shaivilpatel.me→ your public IP, proxied (orange cloud). - Forward router port 443 → homelab:443 (the host nginx). Port 80 optional; Cloudflare does "Always Use HTTPS" at the edge.
4. Install the host nginx site
sudo cp deploy/nginx-host-alts.conf /etc/nginx/conf.d/alts.conf
sudo nginx -t && sudo systemctl reload nginx5. Point the app at the HTTPS origin — in .env:
ALLOWED_ORIGIN=https://alts.shaivilpatel.me
HOST_PORT=5005
ALLOWED_ORIGIN being https:// is what flips the session cookie to
Secure — required for login over HTTPS.
6. Bring it up
docker compose up -d --buildLoad https://alts.shaivilpatel.me and log in.
Optional hardening: restrict inbound :443 to
Cloudflare's IP ranges at your firewall
(or with nginx allow/deny), so nobody can reach the origin directly and
bypass Cloudflare.
Prefer an all-in-Docker setup instead of host nginx? Put Caddy or a Cloudflare Tunnel container in front of
frontend:80and skip the host nginx entirely — the app only needsALLOWED_ORIGIN=https://….
A single shared bearer token (DASHBOARD_TOKEN). Required on every REST
request as Authorization: Bearer <token>, and on the WebSocket upgrade
as ?token=<token>. Failed attempts are rate-limited and IP-logged.
This is a single-user self-host tool — there are no user accounts.
Device-code flow only. When you add a Microsoft account:
- Dashboard shows you a code like
ABCD-EFGH - You go to
microsoft.com/link, paste it, sign in with the Microsoft account that owns the Minecraft profile - Dashboard polls until success, then stores the refresh token blob encrypted in Postgres and immediately connects the bot
After that, the user never sees a code again until the refresh token expires (~90 days, and mineflayer refreshes it transparently on every use).
Microsoft password (ROPC) auth is intentionally not supported. Microsoft's Xbox Live SISU endpoint blocks password flow for 2FA accounts, breaks regularly under policy changes, and is a major security smell. Device code is the only sane path in 2026.
Three Docker services on a private network. Only the frontend port is exposed to the host; the backend talks to Postgres over the internal bridge.
┌──────────┐ ┌──────────────┐ ┌──────────────┐
│ browser │──────▶│ frontend │──────▶│ backend │──┐
│ :5005 │ │ nginx + Vite │ │ Express + WS │ │
└──────────┘ │ proxies /api │ │ AccountMgr │ │
│ + /ws │ │ N mineflayer │ │
└──────────────┘ └──────┬───────┘ │
│ │
▼ ▼
┌──────────┐ ┌────────┐
│ Postgres │ │ MC │
│ 16 │ │ server │
└──────────┘ └────────┘
Stack
- Backend: Bun + TypeScript (strict), Express,
mineflayer,prismarine-auth, Prisma + Postgres, Pino, Zod,ws - Frontend: React 18 + Vite + TypeScript, Tailwind, shadcn/ui, Zustand, React Router
- Prod serving: nginx in the frontend container, reverse-proxying
/api/*and/wsto the backend service
Axiom/
├── backend/ # Express + mineflayer
│ ├── src/
│ │ ├── minecraft/ # AccountManager, BotInstance, behaviors, MS auth
│ │ ├── routes/ # REST + WS
│ │ ├── db/ # Prisma client + crypto helpers
│ │ ├── middleware/ # auth, rate-limit, error handling
│ │ └── index.ts
│ ├── prisma/schema.prisma
│ ├── tests/
│ └── Dockerfile
├── frontend/ # React + Vite + shadcn/ui
│ ├── src/
│ │ ├── routes/ # Login, AccountList, AccountDetail, Settings
│ │ ├── components/ # AccountCard, ChatLog, BehaviorConfigForm, etc.
│ │ ├── lib/ # api, ws, types, utils
│ │ └── store/ # Zustand stores
│ ├── nginx.conf
│ └── Dockerfile
├── docker-compose.yml
├── docker-compose.dev.yml
└── .env.example
| Name | Required | Notes |
|---|---|---|
DASHBOARD_TOKEN |
yes | Bearer token to log in to the UI. Use a long random string. |
TOKEN_ENCRYPTION_KEY |
yes | Exactly 64 hex chars (32 bytes). Used to AES-256-GCM-encrypt Microsoft refresh tokens at rest. Rotating this means existing MS accounts must re-auth. |
POSTGRES_PASSWORD |
yes | Database password. |
POSTGRES_DB / POSTGRES_USER |
no | Default afkbot. |
ALLOWED_ORIGIN |
no | CORS origin. Default http://localhost:5005. |
HOST_PORT |
no | Host port to publish the frontend on. Default 5005. |
LOG_LEVEL |
no | Pino level. Default info. |
Generate the secrets:
openssl rand -hex 32 # DASHBOARD_TOKEN
openssl rand -hex 32 # TOKEN_ENCRYPTION_KEYThe Postgres volume (postgres-data) holds everything important: account
configuration, settings, and the encrypted Microsoft token blobs. Back it
up like any other Postgres database:
docker compose exec postgres pg_dump -U afkbot afkbot > backup.sqlRestoring this on another host requires the same TOKEN_ENCRYPTION_KEY
to decrypt the cached tokens — otherwise Microsoft accounts will need to
re-auth once.
- Stop all bots from the dashboard (or
docker compose down) - Update
TOKEN_ENCRYPTION_KEYin.env - Bring the stack back up — Microsoft accounts will show as needing re-auth on next connect; click the account → run the device-code flow again. Offline accounts are unaffected.
git pull
docker compose build
docker compose up -dSchema changes are applied automatically by prisma migrate deploy on
backend startup.
- The bundled nginx can terminate TLS using a Cloudflare Origin Certificate (see "HTTPS via Cloudflare" below). If you'd rather front it with Caddy / Traefik / a Cloudflare Tunnel, that works too. The dashboard token is the only thing standing between an attacker and full control of your bot accounts, so don't expose it over plain HTTP beyond your LAN.
- The backend container runs as a non-root user (
app). - The backend is not published to the host port — only the frontend's nginx is reachable from outside the compose network.
- Logs redact
Authorizationheaders and any field named*token,*password,*encryptionKey.
Hypixel and most large 1.8.9 servers explicitly ban AFK bots in their rules. This tool is intended for private SMPs, faction servers where automation is allowed, and your own test servers. If you're not sure your server permits this, assume it doesn't and ask the operator. Getting your account banned is not the bot's fault.
pnpm install
pnpm --filter backend prisma:generate
# Backend (needs Postgres reachable — start it with docker compose -f docker-compose.yml -f docker-compose.dev.yml up postgres)
pnpm --filter backend db:migrate:dev
pnpm --filter backend dev
# Frontend (uses the Vite dev proxy at port 5173 → backend at 3000)
pnpm --filter frontend dev
# Tests
pnpm --filter backend test
# Typecheck everything
pnpm typecheck- Discord control (REST + WS surface is already factored to support it; not built)
- Pathfinding, combat, farming — AFK only
- Multi-user / multi-tenant — single shared token, single self-host
- Mojang auth (dead since 2022)
- Microsoft password / ROPC auth (broken + insecure — use device code)