OpenBSD Minecraft Server
OpenBSD makes for an excellent and secure host for Minecraft servers.
My initial reference was rjc's guide Running Minecraft server on OpenBSD. I kept adding onto it while maintaining various public and private servers, results below.
Initial Setup
Configure doas
If doas is not already set up, configure it as root:
echo 'permit persist :wheel' >> /etc/doas.conf
This allows members of the wheel group to use doas with persistence.
Create a Dedicated User
Create a user for running server.jar:
doas useradd -k /var/empty -g=uid -L daemon -d /var/minecraft -m /var/minecraft -s /sbin/nologin -u 998 _minecraft
This ensures the Minecraft server runs with limited privileges.
Configure port forwarding
If needed, update /etc/pf.conf to allow the inbound traffic on the default port 25565:
pass in log on egress proto tcp from any to (egress) port 25565
Reload the firewall rules:
doas pfctl -f /etc/pf.conf
Installing the Minecraft Server
Install Java
The latest Java version is required:
pkg_add jdk--%21
Download Server JAR
Vanilla Server
The vanilla server is often the fastest, simplest, and most reliable option. However, it lacks many nice features of Paper, like accessing RCON via named pipe.
cd /var/minecraft && doas -u _minecraft ftp https://piston-data.mojang.com/v1/objects/145ff0858209bcfc164859ba735d4199aafa1eea/server.jar
Note: Update the URL to reflect whichever version you want to serve.
PaperMC Server
PaperMC is a heavier server and uses more resources, and may be more challenging to dial in on a small virtual host. However, it allows RCON via named pipe, so it has better scriptability.
version="1.21.4"
build="138"
cd /var/minecraft && doas -u _minecraft ftp "https://api.papermc.io/v2/projects/paper/versions/${version}/builds/${build}/downloads/paper-${version}-${build}.jar"
Symlink for Easier Upgrades
doas install -g _minecraft -o _minecraft /dev/null server.jar
doas ln -sf paper-${version}-${build}.jar server.jar
Starting the Server
Start the server once to generate necessary files (e.g. server.properties):
doas -u _minecraft /usr/local/jdk-21/bin/java -Xmx1G -Xms1G -jar /var/minecraft/server.jar --nogui
Server Setup
Accept EULA
doas -u _minecraft sed -i '/^eula/s/false/true/' /var/minecraft/eula.txt
Configure server.properties
Modify key settings:
SEED=8204907420156923486
WORLD_NAME="my_mp_world"
MOTD="My MP World"
MAX_PLAYERS="25"
Apply changes:
doas sed -i \
-e "s/^level-name=.*/level-name=$WORLD_NAME/" \
-e "s/^level-seed=.*/level-seed=$SEED/" \
-e "s/^motd=.*/motd=$MOTD/" \
/var/minecraft/server.properties
You may also want some other common options:
doas sed -i \
-e "s/^enable-rcon=false/enable-rcon=true/" \
-e "s/^enforce-whitelist=false/enforce-whitelist=true/" \
-e "s/^max-players=.*/max-players=$MAX_PLAYERS/" \
/var/minecraft/server.properties
Configure whitelist.json if needed, example:
[
{
"uuid": "e3123330-9c07-42ec-8791-30f0d733eb7a",
"name": "Bits"
}
]
Configure ops.json if needed, example:
[
{
"uuid": "13b589ca-6bd6-407c-9b21-36cc82861704",
"name": "Baos",
"level": 4,
"bypassesPlayerLimit": true
}
]
Remember to set the correct file ownership, if needed:
doas chown _minecraft:_minecraft whitelist.json
doas chown _minecraft:_minecraft ops.json
Copy an existing world (optional)
Worlds are typically stored in one of two formats:
-
Client worlds are contained within a single folder, such as:
My World -
Server worlds (e.g., for Paper) are stored as three separate folders:
My World My World_nether My World_the_end
Copy your existing world folder(s) to /var/minecraft and extract. If your world is in the first single folder format, the server will automatically convert it to the second split structure for you. You will need to revert this change if you ever decide to run the server world on a client again.
Important: Make sure the world directory name matches the level-name property in server.properties.
Important: Make sure the world directories are owned by the _minecraft user:
doas chown -R _minecraft:_minecraft "${WORLD_NAME}"*
Automation
Create rc.d Script
Copy the rc.d script into a file named /var/minecraft/minecraft.rc.d.txt:
#!/bin/ksh
java_version=21
java=/usr/local/jdk-${java_version}/bin/java
daemon_jar="server.jar"
daemon_execdir=/var/minecraft
daemon_user="_minecraft"
daemon_fifo=/var/run/minecraft
# Paper Server
daemon="$java"
daemon_flags="-Xms4G -Xmx4G -jar ${daemon_execdir}/${daemon_jar} --nogui"
# Forge Server
daemon="/bin/sh"
daemon_flags="${daemon_execdir}/run.sh --nogui"
. /etc/rc.d/rc.subr
rc_bg=YES
rc_reload=NO
rc_pre() {
test -p $daemon_fifo || mkfifo -m 0600 $daemon_fifo
chown $daemon_user $daemon_fifo
}
rc_start() {
rc_exec "tail -f $daemon_fifo | ${pexp} >/dev/null 2>&1"
}
rc_stop() {
rc_exec "echo stop | tee -a $daemon_fifo"
}
rc_post() {
pkill -f "tail -f $daemon_fifo"
rm -f $daemon_fifo
}
rc_cmd $1
Install with correct permissions:
doas install -m 555 /var/minecraft/minecraft.rc.d.txt /etc/rc.d/minecraft
Server Management
Adding users to the whitelist
Username and UUID lookups can be performed for free at mcuuid.net.
Simply add the uuid and name properties to /var/minecraft/whitelist.json, or add with the command: /whitelist add <username>.
You could use jq for easier automation:
jq '. + [{"uuid": "PLAYER_UUID", "name": "PLAYER_NAME"}]' whitelist.json > tmp.json && mv tmp.json whitelist.json
External server management via RCON Script
NOTE: This only works on Paper and other servers that allow RCON via named Pipe. Vanilla does not support this.
Create a simple rcon.sh for easy access to the server's console from the shell:
#!/bin/sh
while true; do
printf "RCON> "
read input
echo "$input" | doas -u _minecraft tee -a /var/run/minecraft >/dev/null
done
Monitor Logs
The latest log entries are stored in logs/latest.log. They will be automatically rotated and gzipped by the server regularly.
tail -f /var/minecraft/logs/latest.log
If the server stops unexpectedly, check crash logs in:
ls /var/minecraft/hs_err_pid*.log
Changing the World Seed
You can easily change the world name and seed any time. Keep in mind this will generate a new, fresh world, so backup your existing world if needed.
WORLD_NAME="myopenbsdmc"
SEED=1234
rm -rf /var/minecraft/$WORLD_NAME
sed -i -e "s/^level-seed=.*/level-seed=$SEED/" /var/minecraft/server.properties
rcctl restart minecraft
Upgrading a Paper Server
Stop the Server
rcctl stop minecraft
Download and Install New Version
First, update Java if needed:
doas pkg_add jdk
version="1.21.4"
cd /var/minecraft && doas -u _minecraft ftp "https://api.papermc.io/v2/projects/paper/versions/${version}/builds/128/downloads/paper-${version}-128.jar"
Backup the old JAR and start the new one:
mv server.jar server.jar.backup
mv paper-${version}-128.jar server.jar
rcctl restart minecraft
World Backups
Setup a Cron Job
doas crontab -l -u _minecraft
Example backup job:
0 * * * * /bin/sh /var/minecraft/scripts/backup-world.sh
TODO: Need to store backup-world.sh somewhere.
Forge Setup
Forge is a very different server setup, so it has its own section. Forge allows for modded Minecraft servers.
Installing a Forge Server
Download Forge Installer
version="1.19.2-43.2.8"
cd /var/minecraft
doas -u _minecraft ftp "https://maven.minecraftforge.net/net/minecraftforge/forge/${version}/forge-${version}-installer.jar"
Run the Forge Installer
doas -u _minecraft /usr/local/jdk-17/bin/java -jar forge-${version}-installer.jar --installServer
Start the Forge server
doas -u _minecraft /var/minecraft/run.sh --nogui
Installing Forge Mods
Copy Mods from CurseForge
Source (PC):
C:\Users\$USERNAME\curseforge\minecraft\Instances\$INSTANCE_NAME\mods
Target (Server):
/var/minecraft/mods
Example:
ls /var/minecraft/mods/*.jar | wc -l # Shows mod count
Download Mods Directly
cd /var/minecraft/mods
ftp https://www.curseforge.com/minecraft/mc-mods/guard-villagers/...
rcctl restart minecraft
Adjusting OpenBSD Limits for Forge
Modify /etc/login.conf:
minecraft:\
:ignorenologin:\
:datasize=9000M:\
:maxproc=infinity:\
:openfiles-max=2048:\
:openfiles-cur=256:\
:stacksize-cur=16M:\
:tc=default:
Rebuild the login database:
cap_mkdb /etc/login.conf
Change the _minecraft user class:
usermod -L minecraft _minecraft
Verify the change:
userinfo _minecraft