VPS Tutorials
Set Up a Minecraft Java Server on a VPS (2026): Paper, Docker, Plugins, Whitelist and Backups
Deploy Minecraft Java 26.2 with Paper on a VPS using Docker Compose, with Java 25, whitelist, plugins, RCON, automatic backups, restore drills, safe upgrades and TPS lag troubleshooting.

If you want to play Minecraft Java survival with friends long term, renting a VPS and running your own server is usually more stable than having someone keep their computer on all the time, and it also lets you freely install Paper plugins, manage maps and control backups. But "it starts" and "it runs stably" are very different things: single-core CPU performance, data center latency, memory allocation, view distance, whitelist and world backups all directly affect the experience.
This tutorial uses the current stable Minecraft Java 26.2 and Paper 26.2 as examples, deployed with Docker Compose and itzg/minecraft-server. The configuration covers Java 25, Paper, whitelist, RCON Secret, automatic world backups, plugin installation, upgrades and lag troubleshooting, and is suitable for a small survival server of 3-10 players.
First confirm which client your players use, so you don't discover after building the server that they can't join.
| Scenario | Recommended option | Notes |
|---|---|---|
| All players use Java Edition on Windows, macOS or Linux | Paper Java server | Supports Bukkit/Paper plugins, mature ecosystem |
| Players mainly use phones, consoles or Bedrock Edition | Bedrock Dedicated Server | Default port and server program differ; this configuration cannot be applied directly |
| Manage multiple game servers on one machine or hand over to several admins | Pterodactyl | Has a panel, resource quotas, file and user management, but deployment is more complex |
| Run only one Java survival server for 3-10 players | This article's Docker Compose | Fewer components, clear upgrade and migration path |
Paper is a Minecraft Java server software aimed at production servers, offering better performance, configuration options and a plugin API. It is not a mod loader; if you need Forge, NeoForge or Fabric modpacks, the server type, Java version and client mods must all be matched again.
If you need to manage multiple Minecraft, Rust or CS2 instances, continue with Set Up a Pterodactyl Game Server Panel on a VPS.
The main game loop of a Minecraft server is sensitive to single-core performance. Rather than buying many low-frequency shared vCPUs, prioritize a high-clock, stable-performance CPU, NVMe disk and a data center close to players.
| Scale | Suggested VPS | Java heap | Suggested view distance |
|---|---|---|---|
| 2-4 players pure survival, few plugins | 2-4 cores, 4-6GB, 40GB NVMe | 3-4GB | 6-8 |
| 5-10 players, common admin plugins | 4 cores, 8GB, 60GB NVMe | 6GB | 8 |
| 10-20 players, redstone farms or frequent exploration | 4-6 high-performance cores, 12-16GB | 8-12GB | Start testing at 6-8 |
| Large modpacks or public servers | Benchmark separately | Per modpack requirements | Do not apply this article's parameters directly |
Java heap must not consume the entire VPS. The container, JVM non-heap memory, Docker and Linux all need space; the itzg/minecraft-server documentation recommends reserving about 25% of total container memory for non-heap parts. This article allocates MEMORY=6G on an 8GB VPS.
Choose a data center location close to your main players. Before purchasing, you can use Game Server VPS Recommendations and Configuration Guide to judge network routes, DDoS protection and single-core performance.
The system example is Ubuntu 24.04 LTS. Minecraft Java uses TCP 25565 by default; both the security group and the host firewall must allow this port.
If you want players to join via a domain, add an A record for mc.example.com pointing to the VPS public IPv4. When using Cloudflare DNS, set it to DNS only; the normal orange-cloud proxy does not forward Minecraft TCP traffic.
Check whether the port is occupied:
sudo ss -lntp | grep ':25565'
Continue only if there is no output. Then install Docker Engine and Compose v2, and confirm the versions:
docker --version
docker compose version
Create the data, backup and Secret directories:
sudo mkdir -p /opt/minecraft/{data,backups,secrets}
sudo chown -R "$USER":"$USER" /opt/minecraft
cd /opt/minecraft
openssl rand -base64 36 > secrets/rcon_password
chmod 600 secrets/rcon_password
RCON is used for safe shutdown, executing console commands and coordinating backups. This article does not map 25575 to the public internet; the backup container accesses it only through the Compose internal network.
If you are not yet familiar with health checks, log rotation, resource boundaries and Secret management in production environments, you can first read VPS Docker Compose Production Environment Configuration Guide. The configuration below is tailored for Minecraft with pinned versions, persistence, health dependencies and backup orchestration.
Create /opt/minecraft/compose.yaml:
services:
mc:
image: itzg/minecraft-server:2026.5.4-java25
container_name: minecraft-paper
restart: unless-stopped
tty: true
stdin_open: true
ports:
- "25565:25565/tcp"
environment:
EULA: "TRUE"
TYPE: "PAPER"
VERSION: "26.2"
MEMORY: "6G"
ONLINE_MODE: "TRUE"
ENABLE_WHITELIST: "TRUE"
ENFORCE_WHITELIST: "TRUE"
WHITELIST: |-
PlayerName1
PlayerName2
MAX_PLAYERS: "10"
VIEW_DISTANCE: "8"
SIMULATION_DISTANCE: "6"
DIFFICULTY: "normal"
MOTD: "Private survival server"
ENABLE_RCON: "TRUE"
RCON_PASSWORD_FILE: /run/secrets/rcon_password
STOP_SERVER_ANNOUNCE_DELAY: "30"
TZ: "Asia/Shanghai"
volumes:
- ./data:/data
secrets:
- rcon_password
stop_grace_period: 90s
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
backups:
image: itzg/mc-backup:latest
container_name: minecraft-backups
restart: unless-stopped
depends_on:
mc:
condition: service_healthy
environment:
RCON_HOST: mc
RCON_PASSWORD_FILE: /run/secrets/rcon_password
BACKUP_NAME: minecraft-paper
BACKUP_INTERVAL: "6h"
INITIAL_DELAY: "0"
PRUNE_BACKUPS_DAYS: "7"
TZ: "Asia/Shanghai"
volumes:
- ./data:/data:ro
- ./backups:/backups
secrets:
- rcon_password
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
secrets:
rcon_password:
file: ./secrets/rcon_password
Here the Minecraft server image's tool version and Java 25 are pinned, and the game version is pinned to 26.2. Paper's official documentation states that 26.1 and above require Java 25; if you need to run old worlds or old plugins from 1.20 to 1.21.11, switch to a Java 21 image and re-verify compatibility.
The latest tag of itzg/mc-backup changes. After the first stable verification, you can use docker image inspect to record the current RepoDigest, then pin the digest in the production configuration. Do not automatically follow unknown major versions without a complete backup.
Setting EULA=TRUE means accepting the Minecraft EULA. Before going live, read the current EULA and Usage Guidelines, and ensure the server's charging, content and branding usage comply with them.
First resolve Compose and pull the image:
cd /opt/minecraft
docker compose config >/dev/null
docker compose pull
docker compose up -d
docker compose ps
docker compose logs -f mc
The first start downloads Paper 26.2 and generates the world. After the log shows something like Done, press Ctrl+C to exit the logs; this does not stop the container.
Confirm container health, version and player list:
docker compose ps
docker compose exec mc rcon-cli version
docker compose exec mc rcon-cli list
docker compose exec mc rcon-cli whitelist list
mc should show healthy. The backup container waits for the game server to be healthy before connecting via RCON.
Allow TCP 25565 in the VPS cloud firewall. With UFW you can add:
sudo ufw allow 25565/tcp
sudo ufw status numbered
Docker published ports may bypass plain UFW INPUT rules, so also check the cloud firewall and Docker's forwarding chains. This article only publishes 25565; RCON 25575, the Docker API and SSH management ports must never be exposed unprotected.
Test from a computer on another network:
nc -vz mc.example.com 25565
Players add in Minecraft Java Edition's multiplayer:
mc.example.com
The default port 25565 does not need to be written after the address. When using another port, players need to enter 域名:端口, or you need to configure an SRV record additionally.
Private servers should enable the whitelist by default. WHITELIST in the Compose file resolves usernames at initialization; replace the example player names before going live.
Add or remove players at runtime:
docker compose exec mc rcon-cli whitelist add PlayerName
docker compose exec mc rcon-cli whitelist remove PlayerName
docker compose exec mc rcon-cli whitelist list
Grant administrator permissions:
docker compose exec mc rcon-cli op PlayerName
docker compose exec mc rcon-cli deop PlayerName
Only give OP to trusted administrators. When regular players need teleport, land claim or basic commands, use a permissions plugin to grant least privilege; do not set everyone to OP just for convenience.
Keep ONLINE_MODE=TRUE so the server verifies player accounts. Disabling online verification loses official identity validation and may lead to impersonation logins, inventory and permission chaos; if a proxy network has special requirements, configure it according to Velocity/Paper's secure forwarding scheme rather than disabling it directly.
Paper plugins go in the host directory:
/opt/minecraft/data/plugins/
Installation process:
- Download only from the plugin's official site, Hangar, Modrinth or the project's GitHub;
- Confirm the plugin explicitly supports Paper 26.2 and Java 25;
- Perform a world backup first;
- Put the JAR into
data/plugins/; - Fully restart the server and check the logs.
docker compose exec backups backup now
docker compose restart mc
docker compose logs --tail=200 mc
docker compose exec mc rcon-cli plugins
Do not use /reload or hot-plug plugins. Paper's documentation explicitly states that runtime reloading is not supported; plugin upgrades, removals and dependency changes should all be done via a full restart.
Change only one plugin at a time, and record the version and download source. If the server starts crashing or TPS drops, roll back the last change first rather than replacing all plugins at once.
itzg/mc-backup executes save commands via RCON, pauses world writes, packages the data, then resumes writes. This article backs up every 6 hours and retains for 7 days.
Check backup status:
docker compose logs --tail=200 backups
ls -lh /opt/minecraft/backups
Create a backup immediately:
docker compose exec backups backup now
Local backups cannot handle VPS disk failure, account bans or data center incidents. Encrypt and sync /opt/minecraft/backups to another object storage or off-site server, and set an independent retention period. For a more complete off-site backup and restore drill, see VPS Backup and Restore Drill Guide.
Do not test overwrite restores directly on the production world. Prepare a test VPS or a new empty directory:
- Copy
compose.yaml, the Secret and the selected backup; - Keep the test machine's 25565 closed to the public internet, or switch to another port;
- Confirm the
datadirectory is empty; - Use the backup image's
restore-tar-backupto restore the latest archive; - Start
mcand verify the world, inventories, ender chests, plugin data and whitelist.
Before restoring, list the archives and confirm the time and size are reasonable. A world that loads does not mean the restore is complete; also enter different dimensions and check plugin databases.
World format upgrades for Minecraft 26.2 and Paper may be irreversible. Paper 26.2 release notes explicitly require a backup before upgrading, and state that after upgrading to 26.2 you cannot downgrade to a lower version.
Safe upgrade process:
cd /opt/minecraft
docker compose exec backups backup now
docker compose exec mc rcon-cli save-all flush
docker compose stop mc
# 修改 VERSION 或镜像标签前,先核对 Paper 与插件兼容性
docker compose pull
docker compose up -d mc
docker compose logs -f mc
After verifying the world, all three dimensions, player data and plugins, start the backup service:
docker compose up -d backups
docker compose ps
Do not simply change VERSION to LATEST and restart unattended. Test the new version on a backup copy first, especially checking economy, permissions, land claim and cross-version map plugins.
Paper's target TPS is 20; MSPT must stay below 50 to maintain 20 TPS. When lag occurs, collect data first:
docker stats --no-stream
docker compose exec mc rcon-cli tps
docker compose exec mc rcon-cli mspt
docker compose exec mc rcon-cli "spark profiler start --timeout 300"
Let the problem actually occur during the 5-minute sampling period, then check the report link returned by spark. Do not immediately add RAM just because memory usage is high; chunk generation, entities, hoppers, redstone, a particular plugin and disk I/O can all be bottlenecks.
Prioritize in this order:
- Lower
VIEW_DISTANCEfrom 8 to 6; - Lower
SIMULATION_DISTANCEfrom 6 to 4; - Limit entity-dense farms and continuously loaded chunks;
- Update or remove high-usage plugins based on the spark report;
- Pre-generate the world border for servers with frequent exploration;
- Confirm the VPS has no CPU steal, disk I/O or memory swapping issues.
Simply increasing Java heap may lengthen garbage collection times. After adjusting memory, go through at least one peak period before comparing TPS, MSPT and spark reports.
docker compose ps
docker compose logs --tail=200 mc
sudo ss -lntp | grep ':25565'
If the local machine is listening but external connections time out, check the cloud firewall, host forwarding rules and domain resolution. If the domain mistakenly has Cloudflare's normal proxy enabled, change it to DNS only.
The client must use a Minecraft Java version compatible with the server. Check:
docker compose exec mc rcon-cli version
grep '^VERSION' compose.yaml
Do not connect a Bedrock client directly to a Java server. If you need cross-version or Bedrock interoperability, evaluate ViaVersion, Geyser and Floodgate compatibility and identity security separately.
docker compose logs --tail=300 mc
docker compose config
Common causes include not accepting the EULA, a nonexistent Paper version, wrong Java version, wrong Secret path and data directory permission issues. Do not delete the data directory to "reinstall"; it contains the world and player data.
Confirm both services mount the same rcon_password Secret, and RCON_HOST=mc. After changing the Secret, rebuild both containers:
docker compose up -d --force-recreate mc backups
docker compose logs --tail=100 backups
Do not map 25575 to the public internet for testing; troubleshoot within the Compose internal network.
Whole-machine CPU percentage masks the case where a single core is already maxed out. Run spark to analyze the main thread, and check newly generated chunks, entity AI, hoppers, redstone and plugin tasks. Buying more slow cores usually does not solve main-thread bottlenecks.
- Minecraft Java version, Paper version and Java 25 are compatible
- 25565/TCP is reachable from external networks, RCON 25575 is not exposed to the public internet
- Domain uses DNS only and resolves to the correct VPS
- Whitelist is enabled and example player names are replaced
-
ONLINE_MODE=TRUE, only trusted administrators have OP -
datais persisted, the world still exists after rebuilding the container - Automatic backups execute successfully and have off-site copies
- A restore drill has been completed in a test directory
- Plugin sources, versions and compatibility are recorded
- Backup completed before upgrade and non-downgrade risk verified
- Peak-hour TPS is close to 20, MSPT stays below 50
Only after completing these checks is this VPS a long-term maintainable Minecraft Java server, rather than just a temporary server. For a small survival server, the most important things are not filling it with plugins or adding unlimited memory, but stable single-core performance, reasonable view distance, a strict whitelist and world backups that can actually be restored.