Minecraft Server — Proxmox LXC¶
Migration of a Minecraft server from an Ubuntu VM to a Proxmox LXC container. Guide is tested and corrected.
1. Create the LXC Container (Proxmox UI)¶
In the Proxmox web UI, click Create CT and fill in each tab:
- General: Set hostname (
minecraft), set a root password, and paste your SSH public key - Template: Select your Debian 12 template
- Disks: 20GB minimum — check your world size first on the old VM with
du -sh ~/minecraft - CPU: 4 cores
- Memory: 6144 MB RAM, 512 MB swap
- Network: Bridge
vmbr0, IPv4 DHCP, use router to reserve the IP randomly assigned - DNS: Leave as default — you'll set this manually after boot
Do not start the container yet.
2. Expose /dev/tun for Tailscale¶
On the Proxmox host, edit the container config (replace 113 with your VMID):
nano /etc/pve/lxc/113.conf
Add at the bottom:
lxc.cgroup2.devices.allow: c 10:200 rwm
lxc.mount.entry: /dev/net/tun dev/net/tun none bind,create=file
Note
10:200 is the Linux device number for /dev/net/tun — it is not related to your VMID.
Now start the container from the Proxmox UI.
3. Generate SSH Key (on your workstation)¶
ssh-keygen -t ed25519 -C "your-email@example.com"
Accept the default path (~/.ssh/id_ed25519). Print the public key to copy it:
cat ~/.ssh/id_ed25519.pub
This public key is what you paste into the Proxmox UI during container creation. Proxmox writes it to /root/.ssh/authorized_keys automatically. For future containers, Proxmox remembers it under Datacenter → Permissions → Users → your user → SSH Keys.
4. First Boot & Network Setup¶
apt update && apt upgrade -y
apt install -y curl wget sudo ufw iproute2
5. Create Admin User & Lock Down SSH¶
# Create your admin user
useradd -m -s /bin/bash -G sudo jake
passwd jake
# Copy root's SSH key so jake can log in
mkdir -p /home/jake/.ssh
cp /root/.ssh/authorized_keys /home/jake/.ssh/authorized_keys
chown -R jake:jake /home/jake/.ssh
chmod 700 /home/jake/.ssh
chmod 600 /home/jake/.ssh/authorized_keys
Harden SSH:
nano /etc/ssh/sshd_config
Set these lines:
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
systemctl restart sshd
Test before closing your session
Open a second terminal and confirm you can SSH in as jake before closing your current session:
ssh jake@machine-ip-address
All further steps are run as jake using sudo.
6. Tailscale Setup¶
curl -fsSL https://tailscale.com/install.sh | sh
systemctl enable tailscaled
systemctl start tailscaled
tailscale up --authkey=YOUR_TAILSCALE_AUTH_KEY
Note
Get an auth key from the Tailscale admin console. Use an Ephemeral: No key so the node persists across reboots.
Verify:
tailscale ip -4
tailscale status
7. Install Java¶
Add the Adoptium repo and install Java 25:
sudo apt install -y ca-certificates apt-transport-https gnupg
curl -fsSL https://packages.adoptium.net/artifactory/api/gpg/key/public \
| sudo gpg --dearmor -o /usr/share/keyrings/adoptium.gpg
echo "deb [signed-by=/usr/share/keyrings/adoptium.gpg] \
https://packages.adoptium.net/artifactory/deb bookworm main" \
| sudo tee /etc/apt/sources.list.d/adoptium.list
sudo apt update
sudo apt install -y temurin-25-jdk
# Confirm
java -version
8. Create the Minecraft User & Directory¶
sudo useradd -r -m -U -d /opt/minecraft -s /bin/bash minecraft
sudo mkdir -p /opt/minecraft
sudo chown minecraft:minecraft /opt/minecraft
9. Migrate Server Files¶
Install rsync, then confirm it's also installed on the old VM:
sudo apt install -y rsync
# On the old VM
which rsync
# If missing: sudo apt install -y rsync
Run this from inside the new LXC, replacing OLD_VM_IP with your VM's IP:
sudo rsync -avz --progress steve@OLD_VM_IP:/home/steve/minecraft/ /opt/minecraft/
After the transfer, fix ownership and remove any stale lock files left over from the running VM:
sudo chown -R minecraft:minecraft /opt/minecraft
sudo rm -f /opt/minecraft/world/session.lock
sudo rm -f /opt/minecraft/world_nether/session.lock
sudo rm -f /opt/minecraft/world_the_end/session.lock
Verify the transfer:
ls -lh /opt/minecraft/
# Should show: server.jar, world/, server.properties, eula.txt, etc.
Confirm eula.txt is accepted:
cat /opt/minecraft/eula.txt
# Must contain: eula=true
# If not: echo "eula=true" | sudo tee /opt/minecraft/eula.txt
10. Update PaperMC¶
Install jq, then download the latest stable build directly from the PaperMC API:
sudo apt install -y jq
cd /opt/minecraft
USER_AGENT="minecraft-server-setup/1.0.0 (homelab)"
PAPERMC_URL=$(curl -s -H "User-Agent: $USER_AGENT" \
https://fill.papermc.io/v3/projects/paper/versions/26.1.2/builds | \
jq -r 'first(.[] | select(.channel == "STABLE") | .downloads."server:default".url)')
sudo curl -L -H "User-Agent: $USER_AGENT" -o /opt/minecraft/server.jar "$PAPERMC_URL"
sudo chown minecraft:minecraft /opt/minecraft/server.jar
11. Test the Server Manually¶
Before setting up the service, confirm the server starts:
sudo -u minecraft bash -c "cd /opt/minecraft && java -Xms2G -Xmx5G -jar server.jar nogui"
Wait for Done! in the output. Test that a client can connect, then stop it:
stop
12. Set Up the Systemd Service¶
sudo nano /etc/systemd/system/minecraft.service
[Unit]
Description=Minecraft Server
After=network.target tailscaled.service
Wants=tailscaled.service
[Service]
Type=simple
User=minecraft
Group=minecraft
WorkingDirectory=/opt/minecraft
Environment="HOME=/opt/minecraft"
ExecStart=/usr/bin/java -Xms2G -Xmx5G \
-XX:+UseG1GC \
-XX:+ParallelRefProcEnabled \
-XX:MaxGCPauseMillis=200 \
-jar /opt/minecraft/server.jar nogui
ExecStop=/bin/kill -SIGTERM $MAINPID
TimeoutStopSec=60
Restart=on-failure
RestartSec=10s
[Install]
WantedBy=multi-user.target
Enable and start:
sudo systemctl daemon-reload
sudo systemctl enable minecraft
sudo systemctl start minecraft
# Watch it come up
journalctl -u minecraft -f
# Exit the log with Ctrl+C once you see Done!
13. Firewall¶
sudo ufw allow 22/tcp # SSH
sudo ufw allow 25565/tcp # Minecraft
sudo ufw enable
sudo ufw status
14. Reboot Test¶
sudo reboot
After the container comes back, SSH in as jake and verify everything is running:
# Minecraft
systemctl status minecraft
# Tailscale
tailscale status
# Confirm server is listening
ss -tlnp | grep 25565
All three green means the migration is complete.
Bonus: Custom Skin Server (offline players)¶
A simple Python HTTP server to serve custom skin PNG files for offline-mode players.
Create the service:
sudo nano /etc/systemd/system/skins.service
[Unit]
Description=Skin File Server
After=network.target
[Service]
Type=simple
User=minecraft
WorkingDirectory=/opt/skins
ExecStart=/usr/bin/python3 -m http.server 8080
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=multi-user.target
Enable and start it:
sudo systemctl daemon-reload
sudo systemctl enable skins
sudo systemctl start skins
Open the port:
sudo ufw allow 8080/tcp
Test it:
curl -I http://localhost:8080/MCBK.png
You should get a 200 OK and the skin loader should work. This survives reboots automatically.
Quick Reference¶
| Task | Command |
|---|---|
| View live server log | journalctl -u minecraft -f |
| Exit live log | Ctrl+C |
| Restart the server | sudo systemctl restart minecraft |
| Stop the server | sudo systemctl stop minecraft |
| Check Tailscale peers | tailscale status |
| Check open ports | ss -tlnp |