6.3 KiB
git pu# Hardened Fedora Minecraft Server (Ansible)
Ansible playbook that turns a fresh Fedora 42 server into a hardened, containerized Minecraft game server — Java and Bedrock clients — running with rootless Podman via systemd Quadlet units.
Everything is managed declaratively from harden_fedora_minecraft.yml:
OS hardening, firewall, SELinux, SSH access control, the Podman runtime,
and the Minecraft/Geyser containers are all deployed by one command.
Features
- 🔒 [Hardening] SELinux enforcing, firewalld default-DROP policy, password
policy (pwquality + aging), root account locked, automatic security updates
(
dnf-automatic). - 🔑 [SSH] Key-only authentication for a dedicated admin user, root login
disabled,
AllowUsersrestriction, and afail2banSSH jail. - 🐳 [Runtime] Rootless Podman with user lingering, subuid/subgid ranges, IPv4 forwarding, and the overlay storage driver.
- ⛏️ [Minecraft] Portable Java server built from
itzg/docker-minecraft-server(PAPER by default, version/memory/difficulty configurable). - 🪨 [Geyser] A second container,
geysermc/geyser, in the same pod so Bedrock (phone/console) players can join the Java world. - 🗄️ [Data] World data and backups live on the host in
/srv/minecraftwith correct SELinux labels.
Architecture
Both game containers run in a single Podman pod (shared network
namespace) as the non-root user mcadmin:
Internet
│
┌───────┴────────┐
│ firewall (DROP)│ only the ports below are allowed
└───────┬────────┘
│
pod: minecraft (rootless, user mcadmin)
├── minecraft-server JVM on 0.0.0.0:25565 (Java clients)
│ └── world data → /srv/minecraft/data
└── geyser Bedrock listener :19132/udp
└── config → /srv/minecraft/geyser
└── forwards Bedrock players to 127.0.0.1:25565
| Port | Protocol | Purpose |
|---|---|---|
22 |
TCP | SSH (configurable via ssh_port) |
25565 |
TCP | Minecraft Java server |
25565 |
UDP | Optional Java server UDP (game query) |
19132 |
UDP | Bedrock (Geyser), when enable_geyser: true |
Repository layout
.
├── harden_fedora_minecraft.yml # entry point: hosts: external-apps → 7 roles
├── inventory.yml # target host definition
├── ansible.cfg # inventory + role path + pipelining
├── requirements.yml # Ansible collections (ansible.posix)
├── group_vars/all.yml # shared variables
├── host_vars/external-apps.yml # per-server settings (IP, SSH key, MC tuning)
└── roles/
├── packages/ # base + container tooling, full dnf update, dnf-automatic
├── firewall/ # firewalld: DROP target, open SSH + Minecraft + Geyser
├── selinux/ # enforcing mode + container_file_t context for /srv/minecraft
├── auth/ # admin user, SSH key, sudoers, password policy, lock root
├── ssh_harden/ # sshd drop-in + fail2ban jail
├── podman/ # socket, linger, subuid/subgid, ip_forward, storage
└── minecraft/ # data dirs, Quadlet pod/container units, image pulls
Prerequisites
- Ansible control node (this repo) with Ansible Core ≥ 2.14 and
ansible.posix - A Fedora 42 target server reachable over SSH with an account that has sudo
- The
ansible.userin the inventory must be able tobecomeroot on the target
Quick start
# 1. Install the required collection
ansible-galaxy collection install -r requirements.yml
# 2. Configure the target server (REQUIRED before first run)
# - host_vars/external-apps.yml
# * ansible_host: real IP or DNS name
# * admin_user_ssh_key: your real SSH public key
# * game settings to taste (version, memory, whitelist…)
# 3. Run the playbook
ansible-playbook harden_fedora_minecraft.yml
⚠️
admin_user_ssh_keymust be a real key before the first run — the playbook disables SSH password authentication and root login once thessh_hardenrole executes.
Configuration
All tunables live in host_vars/external-apps.yml (per server) and
group_vars/all.yml (cluster-wide). Highlights:
| Variable | Default | Meaning |
|---|---|---|
admin_user |
mcadmin |
Non-root user running the containers |
minecraft_version |
LATEST |
Server version (1.21.1, …) |
minecraft_server_type |
PAPER |
PAPER / VANILLA / FABRIC / FORGE |
minecraft_memory |
2G |
JVM memory limit |
minecraft_whitelist |
"" |
Comma-separated allowed usernames (empty = off) |
enable_geyser |
true |
Deploy the Bedrock bridge |
geyser_port |
19132 |
Bedrock UDP port |
geyser_auth_type |
online |
offline only with a paired Floodgate key |
ssh_port |
22 |
SSH port used by firewall + sshd |
Operations
After the playbook has run, manage the servers as mcadmin:
sudo machinectl shell mcadmin@ -- systemctl --user status minecraft-server geyser
sudo machinectl shell mcadmin@ -- systemctl --user restart minecraft-server
sudo machinectl shell mcadmin@ -- podman ps # container list
sudo machinectl shell mcadmin@ -- journalctl --user-unit minecraft-server -f
References
- Ansible Documentation
- Fedora Project
- Podman
- Podman Quadlet (systemd.unit) man page
- Rootless Podman tutorial
- itzg/docker-minecraft-server
- GeyserMC — Bedrock ↔ Java bridge
- GeyserMC/Geyser
- Geyser docker image
- Firewalld
- fail2ban
- SELinux quick docs (Fedora)