Cosmic's

Technology & Life

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"
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:

  1. Client worlds are contained within a single folder, such as:

    My World
    
  2. 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