Technology & Life

Summary
How to setup a Minecraft server on OpenBSD
Published
Reading time
6 min
Tags
, , ,

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.

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 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.

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

The latest Java version is required:

pkg_add jdk--%21

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

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

doas -u _minecraft sed -i '/^eula/s/false/true/' /var/minecraft/eula.txt

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

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}"*

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

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

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

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

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

rcctl stop minecraft

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

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 is a very different server setup, so it has its own section. Forge allows for modded Minecraft servers.

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"

doas -u _minecraft /usr/local/jdk-17/bin/java -jar forge-${version}-installer.jar --installServer

doas -u _minecraft /var/minecraft/run.sh --nogui

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

cd /var/minecraft/mods
ftp https://www.curseforge.com/minecraft/mc-mods/guard-villagers/...
rcctl restart minecraft

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

Back to top ↑