Crafty Controller 4.11.0
Overview
Crafty Controller is a free, open-source web panel for creating and running Minecraft servers. From the browser you can create Java or Bedrock servers of any common flavor, start and stop them, watch the live console and send commands, and edit server files. You can also schedule tasks, take and restore backups, and give other people their own accounts with roles that limit what they can touch.
This SecureAnyCloud image runs Crafty Controller 4.11.0 on Ubuntu 24.04 on Amazon EC2. It is one version of the SecureAnyCloud game server listing on AWS Marketplace. You select it as the software version when you subscribe and launch.
What you get:
- A unique admin password, verified before the panel opens. Crafty creates its
adminaccount on the first start of each instance with a random 64-character password. First boot verifies that this password signs in and that a wrong one is refused. It then moves the password into/root/crafty-credentials.txt(mode 0600, root), destroys Crafty's own copy, and only then starts nginx. The image holds no account, password or database. - HTTPS from first boot. nginx serves the panel on port 443 with a certificate made for this instance and redirects port 80 to it. It refuses requests for any other host name, carries the live console WebSocket, refuses cross-site requests, and slows down password guessing on the sign-in route.
- A container that cannot touch the host. Crafty runs from a container built by SecureAnyCloud from upstream's 4.11.0 image, pinned by digest. It runs as crafty (uid 64010), a dedicated system account with no shell, a locked password, no sudo and no other group, not as the ubuntu login user. The container has no Linux capabilities, cannot gain privileges, sees only its own 12 state directories under /var/lib/crafty, and has no access to the Docker socket.
- Patched dependencies, checked at every start. The upstream image ships Python packages and Ubuntu libraries with published security fixes outstanding. The SecureAnyCloud container raises twelve Python packages to fixed releases (hash-pinned) and applies the Ubuntu updates, without changing any Crafty code. Every time the container starts, it reads the versions actually loaded and refuses to start Crafty if any is below its floor. Step 4 shows the check.
- Game servers that cannot reach your network. The game servers Crafty runs need the internet to download server files and talk to Mojang. A firewall chain on the container's bridge rejects private and link-local destinations, and the instance metadata service is blocked for every container. The AMI requires IMDSv2.
- Lockout recovery closed to the network. Upstream's unauthenticated "Forgot Password" recovery creates a temporary superuser. nginx refuses it from the network. You can still use it from the instance itself (see Troubleshooting).
- Exactly the ports you need. TCP 443 (and 80, which redirects) for the panel, TCP 25565 for the default Minecraft Java server, and TCP 22 for SSH. You choose further game ports in one file.
- A patched base. Ubuntu 24.04 LTS is fully patched at build time, with unattended security upgrades switched on.
The image contains Crafty Controller 4.11.0 (GPL-3.0) in the SecureAnyCloud container sac/crafty-controller:4.11.0-sac, Docker Engine, nginx, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent. Crafty downloads the game server software itself (the Minecraft server jar and the Java runtime it needs) when you create a server.
Default user: ubuntu. Architecture: x86_64. Recommended instance type: m5.large.
Prerequisites
- An AWS account subscribed to the listing, an EC2 key pair, and a VPC subnet with a route to the internet. Crafty downloads server files when you create a server.
- A public IPv4 address on the instance (auto-assigned or an Elastic IP), so that you and your players can reach it.
- A security group that allows TCP 443 and 80 (the panel) from the networks you manage from, TCP 25565 (the default Minecraft Java server) from your players, and TCP 22 (SSH) from your own networks only.
- An instance sized for the game servers you will run, not for the panel. A Minecraft Java server needs 2 to 4 GB of memory for a handful of players. An m5.large (8 GB) runs one or two comfortably.
Connecting to your instance
Connect over SSH as the ubuntu user. This is the default user for Crafty Controller 4.11.0 on Ubuntu 24.04.
Launch the instance
Option A: AWS Marketplace console
- Open the SecureAnyCloud game server listing on AWS Marketplace, choose View purchase options, then Subscribe. One subscription covers every version in the listing.
- When the subscription is active, choose Launch your software.
- Choose One-click launch from AWS Marketplace, or Launch from EC2 Console for the full launch wizard. In Version, choose Crafty Controller 4.11.0 on Ubuntu 24.04, then choose your Region.
- Pick an instance type (
m5.largerecommended) and your key pair. - Set the security group. The listing's default group opens SSH (22), HTTP (80), HTTPS (443) and the Minecraft port (25565) to anywhere. Restrict SSH to your own IP address, and restrict 80 and 443 to the networks you manage from.
- Choose your VPC and subnet. Leave the metadata defaults, because the image already requires IMDSv2. Then choose Launch.
Option B: AWS CLI
Run the commands below on your own workstation. They create a security group with the rules above and then launch the instance. Replace the AMI ID with the one shown for your Region on the Marketplace launch page. Replace the VPC, subnet, key pair and CIDR ranges with your own:
SG=$(aws ec2 create-security-group --group-name crafty \
--description "Crafty Controller" --vpc-id vpc-0123456789abcdef0 \
--query GroupId --output text)
aws ec2 authorize-security-group-ingress --group-id "$SG" --protocol tcp --port 22 --cidr <your-mgmt-cidr>
aws ec2 authorize-security-group-ingress --group-id "$SG" --protocol tcp --port 443 --cidr <your-mgmt-cidr>
aws ec2 authorize-security-group-ingress --group-id "$SG" --protocol tcp --port 80 --cidr <your-mgmt-cidr>
aws ec2 authorize-security-group-ingress --group-id "$SG" --protocol tcp --port 25565 --cidr 0.0.0.0/0
aws ec2 run-instances \
--image-id ami-0123456789abcdef0 \
--instance-type m5.large \
--key-name my-key-pair \
--security-group-ids "$SG" \
--subnet-id subnet-0123456789abcdef0 \
--associate-public-ip-address \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=crafty}]'You do not need --metadata-options, because the AMI already requires IMDSv2. To keep the address across a stop and start, associate an Elastic IP.
Connect and find your login
Connect as ubuntu with the key pair you chose:
ssh -i ~/.ssh/my-key-pair.pem ubuntu@<public-ip>First boot takes two to three minutes. It performs these actions in order:
- Sets the name the panel is served on. This is the instance's public IP address until you give it a DNS name.
- Makes the TLS certificate for that name.
- Starts Crafty on loopback.
- Verifies the admin password.
- Writes
/root/crafty-credentials.txt. - Opens nginx.
The commands below show the entries in the credentials file, then the names of every key it holds. The password itself is not shown here:
sudo grep -E '^CRAFTY_(URL|USERNAME)=' /root/crafty-credentials.txt
sudo grep -oE '^[A-Z_]+=' /root/crafty-credentials.txt | tr -d '='
sudo stat -c '%a %U:%G' /root/crafty-credentials.txtCRAFTY_URL=https://34.201.60.154/ CRAFTY_USERNAME=admin CRAFTY_URL CRAFTY_USERNAME CRAFTY_PASSWORD 600 root:rootThe file holds these values:
- CRAFTY_URL: the address of this instance's panel.
- CRAFTY_USERNAME: admin, the superuser account.
- CRAFTY_PASSWORD: the admin password, generated on this instance.
To read the password, run sudo cat /root/crafty-credentials.txt in your own session and store it in your password manager. The file is not updated when you change the password in the panel.
Confirm Crafty is running
Confirm that Docker, the metadata firewall unit, Crafty and nginx are active. nginx serves ports 80 and 443. The panel itself listens only on loopback port 8443, and the one game port published by default is 25565:
systemctl is-active docker.service sac-docker-imds-deny.service crafty.service nginx.service
sudo docker ps --format '{{.Names}} {{.Image}} {{.Status}}' | sed -E 's/Up [^(]*/Up/'
sudo ss -Htlnp | awk '{print $4, $6}' | sed -E 's/,pid=[0-9]+,fd=[0-9]+//g' | sort -u
H=$(sudo sed -n 's/^set \$app_host "\(.*\)";$/\1/p' /etc/nginx/crafty-host.conf)
curl -sk --resolve "$H:443:127.0.0.1" "https://$H/healthz"
curl -sk -o /dev/null -w 'login page: HTTP %{http_code}\n' --resolve "$H:443:127.0.0.1" "https://$H/login"
curl -s -o /dev/null -w 'port 80: HTTP %{http_code}\n' --resolve "$H:80:127.0.0.1" "http://$H/"active active active active crafty sac/crafty-controller:4.11.0-sac Up 0.0.0.0:22 users:(("sshd"),("systemd")) 0.0.0.0:25565 users:(("docker-proxy")) 0.0.0.0:443 users:(("nginx"),("nginx"),("nginx")) 0.0.0.0:80 users:(("nginx"),("nginx"),("nginx")) 127.0.0.1:8443 users:(("docker-proxy")) 127.0.0.53%lo:53 users:(("systemd-resolve")) 127.0.0.54:53 users:(("systemd-resolve")) [::]:22 users:(("sshd"),("systemd")) [::]:25565 users:(("docker-proxy")) [::]:443 users:(("nginx"),("nginx"),("nginx")) [::]:80 users:(("nginx"),("nginx"),("nginx")) ok login page: HTTP 200 port 80: HTTP 301Check the Crafty version and its patched dependencies
The panel reports its own version. The container's version floor check reads the version of each raised package from the running Python interpreter, from the libraries pip carries inside itself, and from the container's Ubuntu package database.
The container runs the same check at every start. If any line fails, it refuses to start Crafty (exit code 78, logged in sudo journalctl -u crafty). Run the checks:
sudo docker exec crafty cat /crafty/app/config/version.json | tr -d ' \n'; echo
sudo docker exec crafty /crafty/.venv/bin/python3 -I /opt/sac/crafty-version-floor.py{"major":4,"minor":11,"sub":0} OK py pillow running=13.0.0 floor=13.0.0 OK py tornado running=6.6.1 floor=6.6.1 OK py anyio running=4.14.2 floor=4.14.2 OK py certifi running=2026.10.23 floor=2026.10.23 OK py cryptography running=50.0.2 floor=50.0.2 OK py pyOpenSSL running=26.4.0 floor=26.4.0 OK py orjson running=3.11.9 floor=3.11.9 OK py PyJWT running=2.15.1 floor=2.15.1 OK py requests running=2.34.2 floor=2.34.2 OK py urllib3 running=2.8.0 floor=2.8.0 OK py setuptools running=84.2.1 floor=84.2.1 OK py pip running=26.3 floor=26.3 OK pip-vendored urllib3 running=2.8.0 floor=2.8.0 OK pip-vendored certifi running=2026.10.23 floor=2026.10.23 OK pip-vendored msgpack running=1.2.4 floor=1.2.4 OK deb openssl running=3.0.13-0ubuntu3.16 floor=3.0.13-0ubuntu3.16 OK deb libssl3t64 running=3.0.13-0ubuntu3.16 floor=3.0.13-0ubuntu3.16 OK deb apt running=2.8.4ubuntu0.1 floor=2.8.4ubuntu0.1 OK deb libapt-pkg6.0t64 running=2.8.4ubuntu0.1 floor=2.8.4ubuntu0.1 OK deb libbrotli1 running=1.1.0-2ubuntu0.1 floor=1.1.0-2ubuntu0.1 VERSION_FLOOR PASS failures=0The container's build recipe, the list of raised packages and the GPL-3.0 license text are on the instance in /usr/share/doc/crafty-controller/.
Sign in to the panel
Open CRAFTY_URL (for example https://34.201.60.154/) in your browser. The certificate is self-signed and made for this instance, so the browser warns you the first time. Accept the warning, or give the instance a DNS name and your own certificate (Step 8).
Sign in as admin with CRAFTY_PASSWORD. Leave the authenticator code empty until you turn on two-factor sign-in for your account.
The dashboard shows the host's CPU and memory use, the space left for backups, and how many servers and players are online. A new instance has no servers yet.
Open your account from the avatar at the top right to change your password and turn on two-factor sign-in.
Create a game server
Choose Create New Server. Pick Minecraft-Java or Minecraft-Bedrock, then the server type (for example Vanilla, Paper or Fabric) and version. Give the server a name, and set the memory and the port under Quick Settings. Keep the port at 25565 for your first server, because that is the port the instance publishes.
Choose Build Server!. Crafty downloads the server files, and the server appears on the dashboard. You can also import an existing server from a zip archive on the right.
Open the server, accept the Minecraft EULA when Crafty asks, and start the server. The Terminal tab shows the live console and accepts commands. The Files, Backups, Schedules and Config tabs manage files, backups, scheduled tasks and settings. Players connect from Minecraft with the instance's address. Port 25565 is the default, so the address alone is enough.
Stop a server before you delete it. Deleting a server that is still stopping can leave Crafty ignoring start, stop and backup actions for every server until the panel restarts. If that happens, run sudo systemctl restart crafty.
Publish more game ports
Only the ports listed in /etc/sac/crafty-game-ports are published from the container to the instance. A fresh instance publishes 25565/tcp. Check the file:
grep -v '^#' /etc/sac/crafty-game-ports
sudo docker port crafty | sort25565/tcp 25565/tcp -> 0.0.0.0:25565 25565/tcp -> [::]:25565 8443/tcp -> 127.0.0.1:8443To run a second server, or a Bedrock server (UDP 19132), add one line per port or range. Use PORT/tcp, PORT/udp or FIRST-LAST/tcp, with ports between 1024 and 65535 and at most 101 ports per line. Open the same ports in the instance's security group, then restart Crafty.
Restarting stops the game servers that are running. Crafty starts again on its own, and servers set to auto-start come back with it.
sudo nano /etc/sac/crafty-game-ports
sudo systemctl restart craftyCheck the security controls
First, check the container account. It is a locked system account in no group but its own. The container runs as that account with no capabilities and no way to gain privileges. The state directories belong to it, and only those 12 directories are mounted, with no Docker socket:
id crafty
sudo passwd -S crafty | cut -d' ' -f1,2 sudo docker inspect --format 'user={{.Config.User}} cap_drop={{.HostConfig.CapDrop}} security_opt={{.HostConfig.SecurityOpt}} privileged={{.HostConfig.Privileged}}' crafty
P=$(sudo docker inspect --format '{{.State.Pid}}' crafty);
sudo grep -E '^(Uid|CapEff):' /proc/$P/status
sudo stat -c '%a %U:%G %n' /var/lib/crafty/config /var/lib/crafty/servers /var/lib/crafty/backups
sudo docker inspect --format '{{range .Mounts}}{{.Source}}{{println}}{{end}}' crafty | grep -c '^/var/lib/crafty/'
sudo docker inspect --format '{{range .Mounts}}{{.Source}}{{println}}{{end}}' crafty | grep -q docker.sock && echo "docker socket mounted" || echo "no docker socket mounted"uid=64010(crafty) gid=64010(crafty) groups=64010(crafty) crafty L user=64010:64010 cap_drop=[ALL] security_opt=[no-new-privileges:true] privileged=false Uid: 64010 64010 64010 64010 CapEff: 0000000000000000 2770 crafty:crafty /var/lib/crafty/config 2770 crafty:crafty /var/lib/crafty/servers 2770 crafty:crafty /var/lib/crafty/backups 12 no docker socket mountedNext, check the network controls:
- The certificate is a plain server certificate made for this instance.
- The firewall drops metadata traffic from every container and rejects private destinations from Crafty's bridge.
- The host units are denied the metadata addresses.
- The Crafty container cannot reach the metadata service.
- The lockout recovery is refused from the network.
H=$(sudo sed -n 's/^set \$app_host "\(.*\)";$/\1/p' /etc/nginx/crafty-host.conf)
openssl s_client -connect 127.0.0.1:443 -servername "$H" </dev/null 2>/dev/null | openssl x509 -noout -subject -enddate -ext basicConstraints,extendedKeyUsage
sudo iptables -S DOCKER-USER
sudo iptables -S SAC-CR-EGRESS | grep -oE '\-d [0-9./]+ -j REJECT' | awk '{print $2}' | paste -sd' ' for u in nginx docker crafty; do echo "$u.service IPAddressDeny=$(systemctl show -p IPAddressDeny --value $u.service)"; done
sudo docker exec crafty /crafty/.venv/bin/python3 -I -c 'import urllib.request as u try: u.urlopen("http://169.254.169.254/latest/meta-data/", timeout=5); print("metadata reachable") except Exception as e: print("metadata from the Crafty container:", type(e).__name__)' curl -sk -o /dev/null -w 'lockout recovery from the network: HTTP %{http_code}\n' --resolve "$H:443:127.0.0.1" "https://$H/api/v2/crafty/resetPass"subject=CN = 34.201.60.154, O = sac notAfter=Feb 13 04:00:30 2029 GMT X509v3 Basic Constraints: critical CA:FALSE X509v3 Extended Key Usage: TLS Web Server Authentication -N DOCKER-USER -A DOCKER-USER -d 168.63.129.16/32 -j DROP -A DOCKER-USER -d 169.254.169.254/32 -j DROP -A DOCKER-USER -i cr-web0 -j SAC-CR-EGRESS 0.0.0.0/8 10.0.0.0/8 100.64.0.0/10 127.0.0.0/8 169.254.0.0/16 172.16.0.0/12 192.0.0.0/24 192.168.0.0/16 198.18.0.0/15 224.0.0.0/4 240.0.0.0/4 nginx.service IPAddressDeny=169.254.169.254/32 168.63.129.16/32 docker.service IPAddressDeny=169.254.169.254/32 168.63.129.16/32 crafty.service IPAddressDeny=169.254.169.254/32 168.63.129.16/32 metadata from the Crafty container: URLError lockout recovery from the network: HTTP 403The blocked private ranges include your VPC, so a game server plugin cannot probe other instances. Replies to players are not affected. The VPC's DNS resolver is the one private address the container may use.
Serve the panel under your own DNS name
Point a DNS record at the instance. An Elastic IP keeps the address stable. Then tell the instance the name with the helper below. The helper does the following:
- Re-issues the self-signed certificate for the name.
- Updates the name nginx accepts, the cross-site guard and Crafty's own base URL.
- Records the name in
/etc/sac/crafty-hostname, which is kept across reboots. - Reloads nginx and updates
CRAFTY_URL.
sudo crafty-set-hostname games.example.comcrafty-set-hostname: Crafty Controller is served at https://games.example.com/Until you set a name, the instance follows its own public IP address. If it comes back from a stop and start with a new address, the certificate and settings are updated at boot.
To use a certificate from your own CA or from Let's Encrypt instead, first set the name with crafty-set-hostname, so that the automatic refresh leaves your certificate alone. Then replace /etc/ssl/crafty/crafty.crt and /etc/ssl/crafty/crafty.key, keeping the key at mode 0600, owner root. Finally, run sudo systemctl reload nginx.
Add users and roles
Open Panel Settings. Under Panel Config, choose Add New User to create an account with its own password. Choose Add New Role to create a role that grants chosen permissions (for example console, files, backups or schedules) on chosen servers.
Give each person their own account rather than sharing admin. Only superusers can change panel settings and branding.
Maintenance
- Logs: Use
sudo docker logs craftyfor the panel,/var/lib/crafty/logs/for Crafty's own logs,/var/log/nginx/for HTTPS access, andsudo journalctl -u crafty-firstboot.service -u crafty-postboot.servicefor first boot. - Restart: Run
sudo systemctl restart crafty(this also stops running game servers) orsudo systemctl restart nginx. - Where your data is: Everything Crafty keeps is under
/var/lib/crafty. Its database and settings are inconfig, the game servers are inservers, and backups are inbackups. Take EBS snapshots of the root volume, and use each server's Backups tab for scheduled backups you can restore from the panel. Crafty keeps backups on the same disk by default, so copy them off the instance as well. - Operating system updates: Ubuntu's unattended security upgrades stay on, so nginx and the rest of Ubuntu's own packages keep receiving security patches. Docker Engine comes from Docker's own apt repository, which unattended upgrades do not cover. Update it, together with everything else, with
sudo apt update && sudo apt upgradein a maintenance window. Upgrading Docker restarts it and Crafty with it, so running game servers stop briefly. Afterwards, check withsystemctl is-active crafty. - Upgrading Crafty: The SecureAnyCloud container is built from upstream's image pinned by digest, and first boot never pulls an image. To move to a later Crafty release, launch the newer SecureAnyCloud version from the listing and move /var/lib/crafty across after backing it up. You can also contact SecureAnyCloud support.
Troubleshooting
The panel does not answer on port 443. On a new instance, nginx waits by design until first boot has verified the admin password. Check with sudo journalctl -u crafty-postboot.service --no-pager | tail -20 and sudo systemctl status crafty nginx.
Crafty does not start. Run sudo journalctl -u crafty --no-pager | tail -30 to see the reason. A line that starts with FAIL followed by dependency version floor FAILED means a package inside the container is older than the fixed release, so Crafty refuses to run. Contact SecureAnyCloud support. A start-up check message about /var/lib/crafty means a state directory's owner was changed. Each directory must stay owned by crafty:crafty (uid 64010).
Start, stop or backup buttons do nothing. This happens after a server was deleted while it was still stopping. Run sudo systemctl restart crafty. In future, stop servers before deleting them.
Players cannot connect. Check that the server is running in the panel. Check that its port is listed in /etc/sac/crafty-game-ports and shown by sudo docker port crafty. Check that the security group allows the port (TCP for Java, UDP for Bedrock).
You lost the admin password. The panel's Forgot Password button is refused from the network. From an SSH session on the instance, start Crafty's recovery on its loopback port. It creates a temporary superuser named anti-lockout-user for one hour and prints its password to Crafty's console. The console output is kept in the container log and in the crafty service journal.
curl -sk https://127.0.0.1:8443/api/v2/crafty/resetPass; echo
sudo docker logs --since 5m crafty 2>&1 | grep -A1 'Username: anti-lockout-user'{"status":"ok","data":"Lockout account has been activated for 1 hour. Please find temporary credentials in the terminal"}Sign in as that user and set a new password for admin under Panel Settings > Users. Then update /root/crafty-credentials.txt with sudo nano, or delete that file, so that it does not hold a stale password.
A request answers 400 Bad Request. The request path contains a backslash, an encoded slash, backslash or dot, or a . or .. segment. nginx refuses these by design. Use the plain path.
The browser says the sign-in is blocked or answers 429. nginx slows down repeated sign-in attempts from one address. Wait a minute, then try again using the exact CRAFTY_URL.
Support
SecureAnyCloud provides 24/7 support for this image by email and live chat. Support covers launching and connecting, game servers and ports, users and roles, backups, TLS and host names, and upgrades.
The Crafty Controller team and contributors develop Crafty Controller, which is released under the GNU General Public License v3.0. The license text, a pointer to the corresponding source and the SecureAnyCloud container's build recipe ship on the instance in /usr/share/doc/crafty-controller/.
Minecraft is a trademark of Mojang Synergies AB. Ubuntu is a registered trademark of Canonical Ltd. Docker is a trademark of Docker, Inc. All other product and company names are trademarks or registered trademarks of their respective holders. SecureAnyCloud is not affiliated with or endorsed by them.
Deploy on AWS
Launch Crafty Controller 4.11.0 on Ubuntu 24.04 on AWS with 24/7 support from SecureAnyCloud.
View on Marketplace
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com