Minecraft Panel
    Java + Bedrock
    Open Source

    Deploy Crafty Controller on a VPS

    A self-hosted multi-server Minecraft control panel — Java + Bedrock, file manager, scheduled backups, REST API, and role-based access — fronted by Nginx with a real Let's Encrypt certificate.

    At a Glance

    ProjectCrafty Controller (by Arcadia Technology)
    LicenseGPLv3
    Recommended PlanCloud VPS 4 GB+ (small vanilla world); 8 GB+ for Paper/modded
    OSUbuntu 24.04 LTS (also Debian 12, Rocky/Alma 9)
    Install PathDocker Compose (recommended) or native Python
    Estimated Setup Time45–60 minutes

    Sizing rule of thumb

    • Vanilla, 5–10 players: 4 GB / 2 vCPU / 60 GB SSD — allocate 2.5 GB to JVM
    • Paper or modded, 10–20 players: 8 GB / 4 vCPU / 100 GB SSD
    • 2–4 servers, mixed Java/Bedrock: 16 GB / 6 vCPU / 200 GB SSD
    • Heavy modpack (ATM, RLCraft): 16 GB+ / 6+ vCPU NVMe — clock speed beats core count

    Disk grows fast: a single world with view-distance=10 often hits 5–15 GB; with 7 dailies + 4 weekly backups plan on 4×–6× live world size.

    1

    Initial Server Setup

    Update + base packages
    ssh root@your.vps.ip
    apt update && apt -y upgrade
    apt -y install curl ca-certificates gnupg lsb-release ufw fail2ban unattended-upgrades
    Create non-root admin
    adduser crafty-admin
    usermod -aG sudo crafty-admin
    mkdir -p /home/crafty-admin/.ssh
    cp /root/.ssh/authorized_keys /home/crafty-admin/.ssh/
    chown -R crafty-admin:crafty-admin /home/crafty-admin/.ssh
    chmod 700 /home/crafty-admin/.ssh
    chmod 600 /home/crafty-admin/.ssh/authorized_keys
    Lock down SSH (/etc/ssh/sshd_config)
    PermitRootLogin no
    PasswordAuthentication no
    Apply + enable auto-updates
    systemctl restart ssh
    dpkg-reconfigure --priority=low unattended-upgrades
    2

    Configure the Firewall

    Define every port up front, then enable UFW once. Note we do not open 8443 — the panel sits behind Nginx on 443.

    UFW rules
    ufw default deny incoming
    ufw default allow outgoing
    ufw allow OpenSSH
    ufw allow 80/tcp     comment 'HTTP for Lets Encrypt'
    ufw allow 443/tcp    comment 'HTTPS reverse proxy'
    ufw allow 25500:25600/tcp comment 'Minecraft Java port range'
    ufw allow 19132/udp  comment 'Minecraft Bedrock'
    ufw allow 8123/tcp   comment 'Dynmap (optional)'
    ufw enable

    25500–25600 matches Crafty's default port pool for new servers. Narrow it only if you'll never run more than one world.

    3

    Install Docker Engine

    Convenience installer
    curl -fsSL https://get.docker.com | sudo sh
    sudo usermod -aG docker crafty-admin
    # log out and back in for the group change to take effect
    4

    Run Crafty Controller

    Project layout
    mkdir -p ~/crafty/docker/{backups,logs,servers,config,import}
    cd ~/crafty
    ~/crafty/docker-compose.yml
    services:
      crafty:
        container_name: crafty_container
        image: registry.gitlab.com/crafty-controller/crafty-4:latest
        restart: always
        environment:
          - TZ=America/New_York
        ports:
          - "127.0.0.1:8443:8443"      # HTTPS panel — localhost only
          - "8123:8123"                 # Dynmap
          - "19132:19132/udp"           # Bedrock
          - "25500-25600:25500-25600"   # Java port range
        volumes:
          - ./docker/backups:/crafty/backups
          - ./docker/logs:/crafty/logs
          - ./docker/servers:/crafty/servers
          - ./docker/config:/crafty/app/config
          - ./docker/import:/crafty/import
    Start + grab the initial admin password
    docker compose up -d
    docker compose logs -f
    # Ctrl+C once you see "Crafty has started, head to https://...:8443"
    
    docker exec crafty_container cat /crafty/app/config/default-creds.txt

    Save the random password. Username is admin. We'll change it through the UI after the proxy is up.

    5

    Reverse Proxy with Nginx + Let's Encrypt

    Crafty's self-signed cert on 8443 is fine for a LAN homelab — on a public VPS you want a real cert. Point an A record for crafty.yourdomain.com at the VPS, then:

    Install nginx + certbot
    sudo apt install -y nginx certbot python3-certbot-nginx
    /etc/nginx/sites-available/crafty
    server {
        listen 80;
        server_name crafty.yourdomain.com;
        return 301 https://$host$request_uri;
    }
    
    server {
        listen 443 ssl http2;
        server_name crafty.yourdomain.com;
    
        client_max_body_size 2048M;
        proxy_buffering off;
        proxy_request_buffering off;
        proxy_read_timeout 600s;
        proxy_send_timeout 600s;
    
        location / {
            proxy_pass https://127.0.0.1:8443;
            proxy_ssl_verify off;       # Crafty's internal cert is self-signed
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }
    }
    Enable + issue cert
    sudo ln -s /etc/nginx/sites-available/crafty /etc/nginx/sites-enabled/
    sudo nginx -t && sudo systemctl reload nginx
    sudo certbot --nginx -d crafty.yourdomain.com
    sudo certbot renew --dry-run

    Don't skip the WebSocket headers. Crafty's per-server terminal view uses one — without Upgrade/Connection the dashboard loads but the in-browser console stays blank.

    6

    First Login + Panel Hardening

    Browse to https://crafty.yourdomain.com, log in as admin, then:

    1. Change the admin password under Panel Config → User Management
    2. Enable two-factor auth on the admin account
    3. Create separate accounts for co-admins with the lowest role that does the job
    4. Under Panel Config → Server Settings, set External IP/DNS so player join links generate correctly
    7

    Create Your First Minecraft Server

    From the dashboard click Create New Server:

    • Server Type: Minecraft Java
    • Source: Download Server JAR — Vanilla latest, or Paper for plugin support
    • RAM: set min == max to avoid GC thrash. 4 GB plan → 2560M; 8 GB → 5120M; leave 1.5 GB for OS + Crafty + cache
    • Port: 25565

    Crafty downloads the jar, generates eula.txt, and creates the entry. Hit Start and watch the terminal pane.

    Optional friendly DNS — an SRV record means players don't need to type the port:

    DNS SRV record
    _minecraft._tcp.yourdomain.com.  86400 IN SRV 0 5 25565 mc.yourdomain.com.
    8

    Backups (Panel + Off-Host)

    In the server's Backups tab, set a daily 4 a.m. schedule, enable compression, max 7 backups, exclude logs/* and cache/*. Backups land in ~/crafty/docker/backups/<server-uuid>/.

    Layer Restic on top to ship them off-host:

    Restic to Backblaze B2
    sudo apt install -y restic
    
    export B2_ACCOUNT_ID="your-key-id"
    export B2_ACCOUNT_KEY="your-application-key"
    export RESTIC_PASSWORD="a-long-passphrase"
    export RESTIC_REPOSITORY="b2:your-bucket-name:crafty-backups"
    
    restic init
    /etc/cron.d/crafty-restic
    0 5 * * * crafty-admin /usr/bin/restic --quiet backup \
      /home/crafty-admin/crafty/docker/backups \
      /home/crafty-admin/crafty/docker/servers \
      /home/crafty-admin/crafty/docker/config \
      && /usr/bin/restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune

    Source the B2 + Restic env vars from /etc/restic/crafty.env (chmod 600) via a wrapper script. Test the restore: restic restore latest --target /tmp/restore-test.

    9

    Operational Hygiene

    • fail2ban for SSH: systemctl enable --now fail2ban
    • Whitelist for private servers: set white-list=true in server.properties — kills 99% of scanner traffic
    • Online mode: leave online-mode=true unless you specifically need cracked clients
    • Monitoring: Netdata installs in one line and gives per-container Docker stats

    Modern Minecraft (1.20.5+) needs Java 21. Older modpacks often want Java 17 or 8. Crafty's Docker image ships all three:

    Per-server JVM paths
    Java 8:  /usr/lib/jvm/java-8-openjdk/jre/bin/java
    Java 17: /usr/lib/jvm/java-17-openjdk/bin/java
    Java 21: /usr/lib/jvm/java-21-openjdk/bin/java
    
    # confirm what's actually present in your image:
    docker exec crafty_container ls /usr/lib/jvm/
    10

    Updating Crafty

    Docker install
    cd ~/crafty
    docker compose pull
    docker compose up -d

    Crafty handles its own DB migrations on container start. Watch the logs after a major version bump. All servers, configs, and backups live in volume mounts so they survive container recreation. Take a Restic snapshot before a major version jump anyway — "good about migrations" isn't the same as "I tested rolling back at 11 p.m. on a Saturday."

    Troubleshooting Cheat Sheet

    • Panel loads but server terminal is blank: WebSocket headers missing in nginx — re-check Upgrade/Connection
    • Container starts then exits: volume permissions — sudo chown -R 1000:1000 ~/crafty/docker
    • Failed to bind to port: port outside Crafty's allowed range — pick something in 25500–25600
    • Players can't connect: test with nc -vz your.vps.ip 25565 — if that fails, UFW; if it succeeds, world is still loading