Skip to content

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