Minecraft Server in FreeBSD Jails Container

Today – as my son requested – we will talk about Minecraft server … inside FreeBSD Jails container.

This is kinda like Docker/Podman thing on Linux – but secure instead.

Prepare

First we will populate /jail path and also fetch needed FreeBSD Base System version.

As for today – we will use 14.2-RELEASE version of FreeBSD UNIX.

host # mkdir -p /jail/minecraft /jail/BASE

host # VER=$( freebsd-version | awk -F '-' '{print $1 "-" $2}' )

host # fetch -o /jail/BASE/${VER}-base.txz https://download.freebsd.org/releases/amd64/${VER}/base.txz

Next we will create our dedicated Minecraft FreeBSD Jail and populate it with its own Base System contents.

We will also copy /var/run/dmesg.boot as Minecraft server looks for it.

host # tar -C /jail/minecraft --unlink -xvf /jail/BASE/${VER}-base.txz

host # cp /var/run/dmesg.boot /jail/minecraft/var/run/

Create

Now we will setup base FreeBSD Jails configuration.

These will be the default for all other Jails unless we redefine them.

host # cat /etc/jail.conf
exec.start      = "/bin/sh /etc/rc";
exec.stop       = "/bin/sh /etc/rc.shutdown";
exec.consolelog = "/var/log/jail_console_${name}.log";
exec.clean;
mount.devfs;

Now out Minecraft Jail config.

We will use em0 LAN interface and 10.0.0.210 IP address.

host # cat /etc/jail.conf.d/minecraft.conf
  minecraft {
  # GLOBAL
    exec.start = "/bin/sh /etc/rc";
    exec.stop  = "/bin/sh /etc/rc.shutdown";
    exec.consolelog = "/var/log/jail_console_${name}.log";
    exec.clean;
    mount.devfs;
    host.hostname = ${name};
    path = /jail/${name};

  # CUSTOM
ย  ip4.addr = 10.0.0.210;
interface = em0;
allow.raw_sockets;
allow.sysvipc;
devfs_ruleset=210;
allow.mount;
enforce_statfs=1;
allow.mount.devfs;
allow.mount.procfs;
allow.mount.fdescfs; }

Below you will also find /etc/devfs.rules ruleset on the host below.

host # grep -A 4 minecraft /etc/devfs.rules
[minecraft=210]
add include $devfsrules_jail
add path 'fd*' unhide

We can now start our Jail.

host # service jail onestart minecraft
Starting jails: minecraft.

host # jls
   JID  IP Address      Hostname                      Path
     1  10.0.0.210      minecraft                     /jail/minecraft

You may as well use mine jmore(8) tool.

host # jmore
JAIL       JID  TYPE  VER     DIR              IFACE   IP(s)
----       ---  ----  ---     ---              -----   -----
classic    -    std   13.2-R  /jail/classic    em0     10.0.0.199
ctld-two   -    vnet  13.2-R  /jail/ctld-two   ${if}b  -
ctld       -    vnet  13.2-R  /jail/ctld       ${if}b  -
fbsdjail   -    std   13.1-R  /jail/fbsdjail   wlan0   10.0.0.43
iscsi      -    vnet  13.2-R  /jail/iscsi      ${if}b  -
minecraft  1    std   14.2-R  /jail/minecraft  em0     10.0.0.210
minio      -    std   14.0-R  /jail/minio      em0     10.0.0.133
nfsd       -    vnet  14.1-R  /jail/nfsd       ${if}b  -
other      -    std   14.1-R  /jail/other      em0     10.0.0.199
sambajail  -    vnet  14.1-R  /jail/sambajail  ${if}b  -
unfs3      -    vnet  14.1-R  /jail/unfs3      ${if}b  -

To make that Minecraft Jail permanently start at boot something like that below would be needed on the host system in the /etc/rc.conf file.

host # grep jail /etc/rc.conf
  jail_enable=YES
  jail_devfs_enable=YES
  jail_list="minecraft"

Configure FreeBSD Jail

Now we will enter the Minecraft Jail.

The jmore minecraft c is equivalent to the well known env PS1='minecraft # ' jexec minecraft /bin/sh command.

host # jmore minecraft c
minecraft # 

Next some basic things such as DNS or switching to latest branch for pkg(8) FreeBSD package manager.

minecraft # echo nameserver 1.1.1.1 > /etc/resolv.conf

minecraft # mkdir -p /usr/local/etc/pkg/repos

minecraft # sed -e 's|quarterly|latest|g' /etc/pkg/FreeBSD.conf > /usr/local/etc/pkg/repos/FreeBSD.conf

minecraft # pkg search -o minecraft
games/minecraft-client         Client for the block building game

Now we will install all other needed packages – as Minecraft server needs to be build using FreeBSD Ports.

minecraft # pkg install gitup bsddialog ccache4 portconfig openjdk21 tmux jless

As we need to build Minecraft server from the FreeBSD Ports because of license that needs to be accepted (or ignored) manually – we will now need to fetch the FreeBSD Ports tree with gitup tool.

At the make config part choose DAEMON option.

minecraft # gitup ports
(...)
#
# Please review the following file(s) for important changes.
#       /usr/ports/UPDATING
#       /usr/ports/mail/dspam/files/UPDATING
#
# Done.

minecraft # cd /usr/ports/games/minecraft-server

minecraft # make config

 +------------|minecraft-server-1.21.4|--------------+
 | 'F1' for Ports Collection help.                   |  
 | +---------- RUN [select at least one] ----------+ |
 | | new (*) DAEMON     Run as a service           | |
 | | new ( ) STANDALONE Run the .jar file directly | |
 | +-----------------------------------------------+ |
 |                [  OK  ]     [Cancel]              |
 +---------------------------------------------------+

Build

Next we will build Minecraft server.

minecraft # echo DISABLE_LICENSES=yes >> /etc/make.conf

minecraft # env BATCH=yes make build install clean
(...)
When you first run minecraft-server, it will populate the file
/usr/local/etc/minecraft-server/eula.txt

It is required to read the EULA, and then set eula=true

- Configuration files can be found in /usr/local/etc/minecraft-server/
- Log and debug output files can be found in /var/log/minecraft-server/
- World files can be found in /var/db/minecraft-server/

Without daemon option:
- To run the server, run /usr/local/bin/minecraft-server
- To edit java's parameters, edit /usr/local/etc/minecraft-server/java-args.txt
- To run with a specific version of Java, set environment variable JAVA_VERSION,
  for example:
    export JAVA_VERSION=22
    /usr/local/bin/minecraft-server
  or:
    JAVA_VERSION=22 /usr/local/bin/minecraft-server

With daemon option:
- The service has been installed with the name 'minecraft'
- To adjust maximum memory usage (-Xmx), use minecraft_memx= in /etc/rc.conf
- To adjust initial memory usage (-Xms), use minecraft_mems= in /etc/rc.conf
- To add other java parameters, use minecraft_args= in /etc/rc.conf
- To run with a specific version of Java, use minecraft_java_version= in /etc/rc.conf
- To see the interactive console, type service minecraft console

===>  Cleaning for minecraft-server-1.21.4

Minecraft Server Configuration

As suggested in the pkg-message we will add additional virtual filesystems to the /etc/fstab file.

We will also make sure they are mounted at Jail start time with /etc/rc.local file.

minecraft # cat << FSTAB >> /etc/fstab
        fdesc   /dev/fd         fdescfs         rw      0       0
        proc    /proc           procfs          rw      0       0
FSTAB

minecraft # echo 'mount -a' >> /etc/rc.local

minecraft # mount -a

minecraft # mount
zroot/jail on / (zfs, local, noatime, nfsv4acls)
devfs on /dev (devfs)
fdescfs on /dev/fd (fdescfs)
procfs on /proc (procfs, local)
devfs on /dev (devfs)

We will not configure Minecraft Jail main /etc/rc.conf config file with needed Minecraft server options.

We will also ‘accept’ EULA and create basic Minecraft server config at /usr/local/etc/minecraft-server/server.properties file.

You may also configure additional Java parameters in the /usr/local/etc/minecraft-server/java-args.txt file.

Increase the values if its too small for your case.

minecraft # cat << RC >> /etc/rc.conf
minecraft_enable=YES
minecraft_mems=1024M
minecraft_memx=1024M
RC

minecraft # echo eula=true > /usr/local/etc/minecraft-server/eula.txt

minecraft # cat << MINECRAFT > /usr/local/etc/minecraft-server/server.properties
enable-jmx-monitoring=false
rcon.port=25575
level-seed=
gamemode=survival
enable-command-block=false
enable-query=false
generator-settings={}
enforce-secure-profile=true
level-name=world
motd=FreeBSD Minecraft Server
query.port=25565
pvp=true
generate-structures=true
max-chained-neighbor-updates=1000000
difficulty=easy
network-compression-threshold=256
max-tick-time=60000
require-resource-pack=false
use-native-transport=true
max-players=20
online-mode=false
enable-status=true
allow-flight=false
initial-disabled-packs=
broadcast-rcon-to-ops=true
view-distance=10
server-ip=
resource-pack-prompt=
allow-nether=true
server-port=25565
enable-rcon=false
sync-chunk-writes=true
resource-pack-id=
op-permission-level=4
prevent-proxy-connections=false
hide-online-players=false
resource-pack=
entity-broadcast-range-percentage=100
simulation-distance=10
rcon.password=
player-idle-timeout=0
force-gamemode=false
rate-limit=0
hardcore=false
white-list=false
broadcast-console-to-ops=true
spawn-npcs=true
spawn-animals=true
log-ips=true
function-permission-level=2
initial-enabled-packs=vanilla
level-type=minecraft\:normal
text-filtering-config=
spawn-monsters=true
enforce-whitelist=false
spawn-protection=16
resource-pack-sha1=
max-world-size=29999984
MINECRAFT

Start

Now it is the time to start the installed and configured Minecraft server.

minecraft # service minecraft start
Starting minecraft.

minecraft # service minecraft status
minecraft is running.

minecraft # sockstat -l4
USER     COMMAND    PID   FD  PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      
mcserver java       33227 103 tcp4   10.0.0.210:25565      *:*
root     syslogd     7809 5   udp4   10.0.0.210:514        *:*

It seems to be running properly – but in case it does not – try this command for the debug purposes.

minecraft # su mcserver -c '/usr/local/bin/java -Xmx1024M -Xms1024M -jar /usr/local/minecraft-server/server.jar nogui'

Connect with Minecraft Client

First – make sure that your client is in the same version as the Minecraft server version – 1.21.4 in my case.

In my case the Minecraft client is started from some random Windows box – as shown below.

Hit Multiplayer button.

Now hist Add Server button – so we will add out FreeBSD based Minecraft server.

Enter your preferred Minecraft server name and the IP address.

After a moment our FreeBSD based Minecraft server will appear on the list.

Now hit the Join Server button to join it.

We will see the Connecting to the server… message.

… and after a moment we are joined to our Minecraft server.

Users

As I am not a Minecraft expert it took me a little to find out a way to define ‘admins’ on that server.

Fortunately I have a working solution ๐Ÿ™‚

When I was writing about mine jmore(8) tool here New jmore(8) FreeBSD Jails List/Manage Tool I initially called it jless(8) but someone notified my that there is already a tool with that name and that its for JSON. We will use it in a moment ๐Ÿ™‚

When someone connects to our server its name and uuid is being added to the /usr/local/etc/minecraft-server/usercache.json file as shown below.

host # cat /jail/minecraft/usr/local/etc/minecraft-server/usercache.json | tr ',' '\n'
[{"name":"antuan"
"uuid":"0d61326c-dfd1-3fa8-ba9d-249d402fb700"
"expiresOn":"2025-05-05 14:04:15 +0000"}
{"name":"antek"
"uuid":"4b520bac-4b31-3c41-8f9b-2781763e5c88"
"expiresOn":"2025-05-05 09:16:08 +0000"}]
host # jless /jail/minecraft/usr/local/etc/minecraft-server/usercache.json | cat [ { "name": "antuan", "uuid": "0d61326c-dfd1-3fa8-ba9d-249d402fb700", "expiresOn": "2025-05-05 14:04:15 +0000" }, { "name": "antek", "uuid": "4b520bac-4b31-3c41-8f9b-2781763e5c88", "expiresOn": "2025-05-05 09:16:08 +0000" } ]

It looks even better with colors from bat(1) command.

Now – to make these users ‘admins’ we need to add them to the /usr/local/etc/minecraft-server/ops.json file and also restart Minecraft server to make it effective.

host # jless /jail/minecraft/usr/local/etc/minecraft-server/ops.json | cat
[
  {
    "uuid": "4b520bac-4b31-3c41-8f9b-2781763e5c88",
    "name": "antek",
    "level": 4,
"bypassesPlayerLimit": false
}, { "uuid": "0d61326c-dfd1-3fa8-ba9d-249d402fb700", "name": "antuan", "level": 4,
"bypassesPlayerLimit": false } ]

Level 4 is the highest level of permissions for the record.

Now my son has all needed commands that he uses like /gamemode or /time for example ๐Ÿ™‚

Summary

Feel free to add your thoughts about other needed configurations about personal Minecraft server.

UPDATE 1 – FreeBSD Jails versus Linux Podman

I do not expected that first sentence will be the reason for most comments – so I also add some details here. Lets have a discussion about differences between security of FreeBSD Jails and Linux Podman containers.

Isolation: With rootless Podman it seems to be on the same level as Jails – but only if You run Podman with SELinux/AppArmor enabled. Without SELinux/AppArmor the Jails offer better isolation. When you run Podman with SELinux/AppArmor and then you add MAC Framework (like mac_sebsd/mac_jail/mac_bsdextended/mac_portacl) the Jails are more isolated again.

Kernel Syscalls Surface: Even rootless Podman has ‘full’ syscall access unless blocked by seccomp (SELinux). Jails have restricted use of syscalls without any additional tools – and that can be also narrowed even more with MAC Framework on FreeBSD.

Firewall: You can not run firewall inside rootless Podman container. You can run entire network stack and any firewall like PF or IPFW independently from the host inside VNET Jail – which means more security.

TL;DR: FreeBSD Jails are generally more secure out-of-the-box compared to Podman containers and even more secure if you take the time to add additional layers of security.

Time on the market is also important factor.

Jails are in production since 1999/2000 when they were introduced – so 25 years strong – very well battle tested. Docker is with us since 2014 so that means about 10 years less – but we must compare Jails to Podman.ย Rootless support for Podman first appeared late 2019 (in 1.6 version) so only less then 6 years on the market.

That means Jails are the most battle tested of all of them.

EOF