Hosting Minecraft Servers + BungeeCord with FreeNAS

Published:


How I set up multiple minecraft servers in FreeNAS with a bungeecord/waterfall proxy.

This guide assumes you’re running a FreeNAS server on a local network. If not, you’ll have to update your ip address and default gateway settings accordingly.

Dynmap rendering of our FreeNAS minecaft server

1. Prerequisites

  • Console access to a server running FreeNAS

(Don’t download these yet, read on)

This guide will be using Paper and Waterfall as they are more performant versions of spigot and bungeecord, respectively. They also fully support spigot/bungee plugins.

2. Preparing the Jails

The first thing we’ll need to do is create the main jail (the one running bungeecord or waterfall). Alter the version for the current one your server is running.

echo '{"pkgs":["openjdk12", "curl"]}' > /tmp/minecraft.json

When a jail is created, pkg isn’t installed by default. Using this saves us some commands in the next step.

iocage create \
  --name <name> \
  --pkglist /tmp/minecraft.json
  --release 11.2-RELEASE \
  ip4_addr="vnet0|192.168.1.xxx/24" \
  defaultrouter="192.168.1.1" \
  vnet="on" \
  allow_raw_sockets="1" \
  boot="on"

ip4_addr and defaultrouter configure the network for our jail. If you’re running the server on your local network, then it’s likely the address of your router. (Typically x.x.x.1 or x.x.x.255, consult your router’s manual). ip4_addr is the address you want to assign to the jail.

For name: If you’re just creating a single server, the name minecraft would suffice, but I recommend a structure like:

mc_main
mc_survival
mc_creative
...etc

Next, let’s create directories in the jails and mount them in the root system:

# Creates the directories in the root system
mkdir -p /xxx/data/minecraft/mc_main
mkdir -p /xxx/data/minecraft/waterfall

# Creates the directories in the jail
iocage exec mc_main mkdir /root/minecraft
iocage exec mc_main mkdir /root/waterfall

# Mounts the directories in the root system
iocage fstab -a mc_main \
  /xxx/data/minecraft/mc_main /root/minecraft nullfs rw 0 0
iocage fstab -a mc_main \
  /xxx/data/minecraft/waterfall /root/waterfall nullfs rw 0 0

Change /xxx/data/ to a relevant filepath for your setup. My main ZFS pool is named lily, so I put my minecraft folders under /mnt/lily/data/.

3. Setting up Minecraft

We can access our jails with:

iocage console mc_main

Now we can cd to the minecraft directory and download our server jarfile:

cd /root/minecraft

# paper
curl -L 'https://papermc.io/api/v1/paper/1.15.1/27/download' -o paper-1.15.1_27.jar

Run your server jarfile with:

java -jar /root/minecraft/paper-1.15.1_27.jar

The process should end shortly, printing text about a eula.txt. Edit the file with:

sed -i .bak 's/eula=false/eula=true/g' /root/minecraft/eula.txt

We also need to edit the server.properties file. Open it and change the server port to 25566. We’re changing this value because our proxy server (bungeecord/waterfall) will listen to the default port later.

Now we can run the server!

java -Xms1024M -Xmx1024M -jar /root/minecraft/<name>.jar

Observe the text output for any errors, and then go ahead and connect to it! (Go to the Multiplayer tab, click Add Server, and type in the ip address of your server, e.g. 192.168.1.xx:25566) Once you join, text should get printed to the screen.

However, we have an issue. The server is currently running in an interactive session (i.e. we ran it manually). We need it to run by itself, in a noninteractive session. Some guides will recommend using screen or tmux, but I strongly recommend not using them. If your server ever restarts, you will have to go in manually and restart screen/tmux, and then your minecraft server.

It’s far better to let rc.d manage it for us. Add this script to /usr/local/etc/minecraftd:

#!/bin/sh
#
# PROVIDE: minecraftd
# REQUIRE: LOGIN DAEMON NETWORKING mountcritlocal
# KEYWORD: shutdown
#
# Use the following variables to configure the minecraft server. For example, to
# configure the ON/OFF knob variable:
# sysrc minecraftd_enable="YES"
#
# minecraftd_enable="YES"
# minecraftd_user_dir="/root/minecraft"
# minecraftd_jar_path="/root/minecraft/server.jar"
# minecraftd_java_opts="-Xms1024M -Xmx1024M"

. /etc/rc.subr

name=minecraftd
rcvar=`set_rcvar`
pidfile=/var/run/minecraftd.pid

load_rc_config $name

start_cmd="${name}_start"
stop_cmd="${name}_stop"
status_cmd="${name}_status"

: ${minecraftd_enable="NO"}
: ${minecraftd_user_dir="/root/minecraft"}
: ${minecraftd_jar_path="/root/minecraft/server.jar"}
: ${minecraftd_java_opts="-Xms1024M -Xmx1024M"}

minecraftd_start() {
    if [ -e $pidfile ]; then
        echo "$name already running."
    else
        echo "Starting $name..."
        /usr/sbin/daemon -f -p $pidfile \
            /usr/local/bin/java -Duser.dir=$minecraftd_user_dir \
            $minecraftd_java_opts \
            -jar $minecraftd_jar_path nogui
        echo "$name started."
    fi
}

minecraftd_stop() {
    if [ -e $pidfile ]; then
        echo "Stopping $name..."
        cat $pidfile | xargs kill
        echo "Stopped."
    else
        echo "$name is not running."
    fi
}

minecraftd_status() {
    if [ -e $pidfile ]; then
        echo "$name is running."
    else
        echo "$name is not running."
    fi
}

run_rc_command $1

We have to make the service file executable, so run:

chmod +x /usr/local/etc/rc.d/minecraftd

Essentially, this script lets us not have to manage the server process manually. However we also need to update some settings:

sysrc minecraftd_enable="YES"
sysrc minecraftd_jar_path="/root/minecraft/server.jar"
sysrc minecraftd_java_opts="-Xms1G -Xmx1G"

Make sure to change the minecraftd_jar_path to reflect your downloaded jarfile, and minecraftd_java_opts for how much memory you want to give it.

Now you should be able to the following:

service minecraftd start

Confirm it with:

ps aux | grep openjdk

You should see something like:

root  72816  1.2  2.5 3156872 1273052  -  IJ   Fri19   99:04.12 /usr/local/openjdk12/bin/java -Duser.dir=/root/minecraft -Xms1G -Xmx1G -jar /root/minecraft/paper-1.15.1_27.jar

If not, double check your settings in /etc/rc.conf, and make sure they point to the right files. Manually run the command to make sure it’s not a minecraft configuration issue (i.e. no warnings/errors show up in the console).

Connect to the server again to make sure it works, then stop the server with:

service minecraftd stop

In order for bungee/waterfall to work, we need to edit the server.properties file again. Change online-mode to false.

In spigot.yml, update bungeecord to true.

In paper.yml, update bungee-online-mode to true.

In bukkit.yml, update connection-throttle to -1.

That’s it! Now we just need to set up our proxy to get access to the server again.

4. Setting up Waterfall/Bungeecord

If you’re not in the mc_main jail already, access it with:

iocage console mc_main

Now can cd to the waterfall directory and download waterfall.jar.

cd /root/waterfall

The process for the jarfile is the same as before, except we’re going to the waterfall directory now.

cd /root/waterfall

# waterfall
curl -L 'https://papermc.io/api/v1/waterfall/1.15/309/download' -o waterfall-1.15_309.jar

Run the proxy jarfile with:

java -jar /root/waterfall/waterfall-1.15_309.jar

If necessary, edit eula.txt again:

sed -i .bak 's/eula=false/eula=true/g' /root/waterfall/eula.txt

Now we need to edit config.yml. Look for the servers section, and change it to the following:

servers:
  hub:
    motd: '&1My amazing hub server'
    address: localhost:25566
    restricted: false

Under listeners, change priorities to:

priorities:
  - hub

Change host to:

  host: 0.0.0.0:25565

Finally, set ip_forward: true.

In order to run waterfall noninteractively, we’ll use a similar rc.d script like before:

#!/bin/sh
#
# PROVIDE: waterfall
# REQUIRE: LOGIN DAEMON NETWORKING mountcritlocal
# KEYWORD: shutdown
#
# Use the following variables to configure the minecraft server. For example, to
# configure the ON/OFF knob variable:
# sysrc waterfall_enable="YES"
#
# waterfall_enable="YES"
# waterfall_user_dir="/root/waterfall"
# waterfall_jar_path="/root/waterfall/waterfall.jar"
# waterfall_java_opts="-Xms512M -Xmx1024M"

. /etc/rc.subr

name=waterfall
rcvar=`set_rcvar`
pidfile=/var/run/waterfall.pid

load_rc_config $name

start_cmd="${name}_start"
stop_cmd="${name}_stop"
status_cmd="${name}_status"

: ${waterfall_enable="NO"}
: ${waterfall_user_dir="/root/waterfall"}
: ${waterfall_jar_path="/root/waterfall/waterfall.jar"}
: ${waterfall_java_opts="-Xms512M -Xmx1024M"}

waterfall_start() {
    if [ -e $pidfile ]; then
        echo "$name already running."
    else
        echo "Starting $name..."
        cd $waterfall_user_dir
        /usr/sbin/daemon -f -p $pidfile \
            /usr/local/bin/java -Duser.dir=$waterfall_user_dir \
            $waterfall_java_opts \
            -jar $waterfall_jar_path  nogui
        echo "$name started."
    fi
}

waterfall_stop() {
    if [ -e $pidfile ]; then
        echo "Stopping $name..."
        cat $pidfile | xargs kill
        echo "Stopped."
    else
        echo "$name is not running."
    fi
}

waterfall_status() {
    if [ -e $pidfile ]; then
        echo "$name is running."
    else
        echo "$name is not running."
    fi
}

run_rc_command $1

We have to make the service file executable again, so run:

chmod +x /usr/local/etc/rc.d/waterfall

Like before, we’ll need to edit some settings:

sysrc waterfall_enable="YES"
sysrc waterfall_jar_path="/root/waterfall/waterfall-1.15_309.jar"

Now run the following and connect to your server!

service waterfall start

Congrats, you’ve set up a minecraft server and a proxy server in FreeNAS!

For more servers, create more jails with the instructions from earlier, and follow the same server javafile setup. In server.properties, change ip-address to the address of the jail, and update waterfall’s config to something like below:

servers:
  hub:
    motd: 'My hub server'
    address: localhost:25566
    restricted: false
  creative:
    motd: 'My creative server'
    address: 192.168.1.21:25566
    restricted: false
  survival:
    motd: 'My survival server'
    address: 192.168.1.22:25566
    restricted: false

5. Databases for Plugins

Many minecraft plugins can use mysql or postgres for storage. I highly recommend setting up mysql in a jail and connecting your plugins to it. Here’s how you can do it:

echo '{"pkgs":["mysql80-server"]}' > /tmp/mysql.json
iocage create \
  --name mysql \
  --pkglist /tmp/minecraft.json
  --release 11.2-RELEASE
  ip4_addr="vnet0|192.168.1.xxx/24" \
  defaultrouter="192.168.1.1" \
  vnet="on" \
  boot="on"
# Creates the directories in the root system
mkdir -p /xxx/configs/mysql
mkdir -p /xxx/data/mysql

# Creates the directories in the jail
iocage exec mysql mkdir /config
iocage exec mysql mkdir /data

# Mounts the directories in the root system
iocage fstab -a mysql \
  /xxx/configs/mysql /config nullfs rw 0 0
iocage fstab -a mysql \
  /xxx/data/mysql /data nullfs rw 0 0
iocage console mysql
sysrc mysql_enable="YES"
sysrc mysql_dbdir="/data"
sysrc mysql_confdir="/config"
sysrc mysql_optfile="/config/my.cnf"
cp /usr/local/etc/mysql/my.cnf /config/my.cnf

Now edit /config/my.cnf and change bind-address to 0.0.0.0. This lets us connect remotely, except for root. We don’t want to run things as root, anyway.

Connect to the local mysql database after creating the root user and password:

service mysql-server start
mysql_secure_installation
mysql -uroot -p

Once connected, create the database and user that the plugin will be using. For example, if we were making a database for coreprotect:

create database coreprotect_hub;

CREATE USER 'coreprotect'@'192.168.1.0/255.255.255.0' IDENTIFIED WITH mysql_native_password BY 'password';

grant all privileges on coreprotect_hub.* to 'coreprotect'@'192.168.1.0/255.255.255.0';

The reason we’re using @'192.168.1.0/255.255.255.0' is to allow remote connections, but only within the local network.

Now update your plugin’s config.yml, where host is the ip address of the mysql jail, and port is 3306.

It’s a good idea to create a separate database and user for each plugin and server. For example, I have the databases coreprotect_survival, and coreprotect_creative, both of which have different users.

References