Let's set up a Minecraft server inside an LXD container and configure automatic backups.

The Big Picture

The purpose of this guide is to run Minecraft inside a portable Linux container where...

  • the server can be moved/copied between hosts
  • the container is safe from failed upgrades via snapshot/rollback features
  • the host is protected from most Minecraft-driven vulnerabilities by isolating the server processes from the host system
  • dependencies are isolated from the host system to reduce environment pollution
  • automatic backups are taken via a scheduled script

LXD Container Setup

First, you will need to ensure LXD is installed on the host system and ready to launch containers. The setup instructions vary by Linux distro so your process may be different from mine. Please consult the official setup documentation for instructions.

Now it is time to make the container. Since Minecraft usually benefits from using the latest Java version, I will be creating an Arch Linux container for this tutorial. After listing available Arch Linux images with lxc image list images: arch amd64, we can see that our container should be launched using the archlinux/cloud image.

Note: Using a rolling release distro like Arch Linux is not typically recommended for server use. Thankfully, LXD has a snapshot/rollback system which can help recover a broken system after failed updates. Just make sure you use lxc snapshot before updating so you can use lxc rollback on a failed upgrade.

cait@athena ~> lxc image list images: arch amd64
+----------------------------------+--------------+--------+------------------------------------------+--------------+-----------------+-----------+-------------------------------+
|              ALIAS               | FINGERPRINT  | PUBLIC |               DESCRIPTION                | ARCHITECTURE |      TYPE       |   SIZE    |          UPLOAD DATE          |
+----------------------------------+--------------+--------+------------------------------------------+--------------+-----------------+-----------+-------------------------------+
| archlinux (5 more)               | 3bf6e3fd8ac9 | yes    | Archlinux current amd64 (20231117_04:18) | x86_64       | VIRTUAL-MACHINE | 537.95MB  | Nov 17, 2023 at 12:00am (UTC) |
+----------------------------------+--------------+--------+------------------------------------------+--------------+-----------------+-----------+-------------------------------+
| archlinux (5 more)               | a560cb0206b5 | yes    | Archlinux current amd64 (20231117_04:18) | x86_64       | CONTAINER       | 189.48MB  | Nov 17, 2023 at 12:00am (UTC) |
+----------------------------------+--------------+--------+------------------------------------------+--------------+-----------------+-----------+-------------------------------+
| archlinux/cloud (3 more)         | c1cb4cc1da16 | yes    | Archlinux current amd64 (20231117_04:18) | x86_64       | CONTAINER       | 212.71MB  | Nov 17, 2023 at 12:00am (UTC) |
+----------------------------------+--------------+--------+------------------------------------------+--------------+-----------------+-----------+-------------------------------+
| archlinux/cloud (3 more)         | f6ffac690baa | yes    | Archlinux current amd64 (20231117_04:18) | x86_64       | VIRTUAL-MACHINE | 553.92MB  | Nov 17, 2023 at 12:00am (UTC) |
+----------------------------------+--------------+--------+------------------------------------------+--------------+-----------------+-----------+-------------------------------+
| archlinux/desktop-gnome (3 more) | 6c31f85cc316 | yes    | Archlinux current amd64 (20231117_04:18) | x86_64       | VIRTUAL-MACHINE | 1296.61MB | Nov 17, 2023 at 12:00am (UTC) |
+----------------------------------+--------------+--------+------------------------------------------+--------------+-----------------+-----------+-------------------------------+
cait@athena ~>

It is time to launch the container. I named the container demo in this tutorial but you can pick any name you wish. The command to launch the server is lxc launch images:archlinux/cloud demo. You can verify the server is running by using lxc list demo.

cait@athena ~> lxc launch images:archlinux/cloud demo
Creating demo
Starting demo                               
cait@athena ~> lxc list demo
+------+---------+----------------------+-----------------------------------------------+-----------+-----------+
| NAME |  STATE  |         IPV4         |                     IPV6                      |   TYPE    | SNAPSHOTS |
+------+---------+----------------------+-----------------------------------------------+-----------+-----------+
| demo | RUNNING | 10.97.101.130 (eth0) | fd42:f75c:907b:627b:216:3eff:fe32:df7c (eth0) | CONTAINER | 0         |
+------+---------+----------------------+-----------------------------------------------+-----------+-----------+
cait@athena ~>

Let's go ahead and set up the port forwarding. Minecraft normally runs a TCP connection on port 25565. I will use 25566 instead of the default since this server host is already running a Minecraft server. In LXD syntax, the command to create a port proxy is lxc config device add demo tcp25566 proxy listen=tcp:0.0.0.0:25566 connect=tcp:127.0.0.1:25566. This command means "configure the demo container to have a rule called tcp25566 which proxies a tcp socket on port 25566 across all host network interfaces to a tcp socket on port 25566 on the container's localhost". The command lxc config device show demo will show our new proxy rule.

cait@athena ~> lxc config device show demo
tcp25566:
  connect: tcp:127.0.0.1:25566
  listen: tcp:0.0.0.0:25566
  type: proxy
cait@athena ~>

Arch Linux Configuration

It is time to enter the container and set up the Minecraft service. Are you ready for the easy part? Good. Use lxc shell demo to launch a shell inside the container.

cait@athena ~> lxc shell demo
[root@demo ~]# 

Firstly, we need to upgrade the container to the latest version of Arch Linux. Run pacman -Syyu. While -Syyu is not recommended for upgrades, it is recommended for first-use.

[root@demo ~]# pacman -Syyu
:: Synchronizing package databases...
 core                   132.8 KiB   137 KiB/s 00:01 [###########################] 100%
 extra                    8.3 MiB  1120 KiB/s 00:08 [###########################] 100%
:: Starting full system upgrade...
 there is nothing to do
[root@demo ~]#

We will be editing a lot of configuration files in this tutorial so I recommend installing your preferred text editor. For me, I will be installing Kakoune as my text editor. You will also need to install git, cronie, jdk-openjdk, wget, zip and unzip. The command for my package list ends up as pacman -S git kakoune cronie jdk-openjdk wget zip unzip.

[root@demo ~]# pacman -S git kakoune cronie jdk-openjdk wget zip unzip
resolving dependencies...
looking for conflicting packages...
warning: dependency cycle detected:
warning: harfbuzz will be installed before its freetype2 dependency

Packages (25) freetype2-2.13.2-1  giflib-5.2.1-2  graphite-1:1.3.14-3
              harfbuzz-8.3.0-1  hicolor-icon-theme-0.17-3
              java-environment-common-3-5  java-runtime-common-3-5  jbigkit-2.1-7
              lcms2-2.15-1  libjpeg-turbo-3.0.1-1  libnet-2:1.3-1  libpng-1.6.40-2
              libtiff-4.6.0-1  nspr-4.35-1  nss-3.94-1  perl-error-0.17029-5
              perl-mailtools-2.21-7  perl-timedate-2.33-5  cronie-1.7.0-4
              git-2.42.1-1  jdk-openjdk-21.u35-8  kakoune-2023.07.29-1  unzip-6.0-20
              wget-1.21.4-1  zip-3.0-11

Total Download Size:    471.69 MiB
Total Installed Size:  1193.15 MiB

:: Proceed with installation? [Y/n] y
:: Retrieving packages...
 jdk-openjdk-21.u3...   457.5 MiB  3.05 MiB/s 02:30 [###########################] 100%
 git-2.42.1-1-x86_64      6.2 MiB  2.94 MiB/s 00:02 [###########################] 100%
 nss-3.94-1-x86_64     1617.6 KiB  2.18 MiB/s 00:01 [###########################] 100%
 kakoune-2023.07.2...  1181.5 KiB  1818 KiB/s 00:01 [###########################] 100%
 harfbuzz-8.3.0-1-...  1008.4 KiB  1681 KiB/s 00:01 [###########################] 100%
 libtiff-4.6.0-1-x...   961.1 KiB  1197 KiB/s 00:01 [###########################] 100%
 wget-1.21.4-1-x86_64   731.9 KiB  1515 KiB/s 00:00 [###########################] 100%
 libjpeg-turbo-3.0...   536.9 KiB  1209 KiB/s 00:00 [###########################] 100%
 freetype2-2.13.2-...   523.2 KiB  1123 KiB/s 00:00 [###########################] 100%
 libnet-2:1.3-1-x86_64  295.0 KiB   922 KiB/s 00:00 [###########################] 100%
 libpng-1.6.40-2-x...   248.5 KiB   866 KiB/s 00:00 [###########################] 100%
 lcms2-2.15-1-x86_64    214.9 KiB   776 KiB/s 00:00 [###########################] 100%
 nspr-4.35-1-x86_64     198.3 KiB   734 KiB/s 00:00 [###########################] 100%
 zip-3.0-11-x86_64      169.9 KiB   708 KiB/s 00:00 [###########################] 100%
 unzip-6.0-20-x86_64    142.3 KiB   619 KiB/s 00:00 [###########################] 100%
 cronie-1.7.0-4-x86_64   91.1 KiB   472 KiB/s 00:00 [###########################] 100%
 graphite-1:1.3.14...    84.0 KiB   442 KiB/s 00:00 [###########################] 100%
 giflib-5.2.1-2-x86_64   73.7 KiB   388 KiB/s 00:00 [###########################] 100%
 perl-mailtools-2....    58.6 KiB   319 KiB/s 00:00 [###########################] 100%
 jbigkit-2.1-7-x86_64    51.8 KiB   283 KiB/s 00:00 [###########################] 100%
 perl-timedate-2.3...    34.0 KiB   192 KiB/s 00:00 [###########################] 100%
 perl-error-0.1702...    21.4 KiB   124 KiB/s 00:00 [###########################] 100%
 hicolor-icon-them...     9.8 KiB  56.6 KiB/s 00:00 [###########################] 100%
 java-runtime-comm...     5.0 KiB  29.7 KiB/s 00:00 [###########################] 100%
 java-environment-...     2.6 KiB  15.4 KiB/s 00:00 [###########################] 100%
 Total (25/25)          471.7 MiB  2.93 MiB/s 02:41 [###########################] 100%
(25/25) checking keys in keyring                    [###########################] 100%
(25/25) checking package integrity                  [###########################] 100%
(25/25) loading package files                       [###########################] 100%
(25/25) checking for file conflicts                 [###########################] 100%
(25/25) checking available disk space               [###########################] 100%
:: Processing package changes...
( 1/25) installing perl-error                       [###########################] 100%
( 2/25) installing perl-timedate                    [###########################] 100%
( 3/25) installing perl-mailtools                   [###########################] 100%
( 4/25) installing git                              [###########################] 100%
Optional dependencies for git
    tk: gitk and git gui
    openssh: ssh transport and crypto [installed]
    perl-libwww: git svn
    perl-term-readkey: git svn and interactive.singlekey setting
    perl-io-socket-ssl: git send-email TLS support
    perl-authen-sasl: git send-email TLS support
    perl-mediawiki-api: git mediawiki support
    perl-datetime-format-iso8601: git mediawiki support
    perl-lwp-protocol-https: git mediawiki https support
    perl-cgi: gitweb (web interface) support
    python: git svn & git p4 [installed]
    subversion: git svn
    org.freedesktop.secrets: keyring credential helper
    libsecret: libsecret credential helper [installed]
( 5/25) installing kakoune                          [###########################] 100%
Optional dependencies for kakoune
    aspell: spell check, correct text
    clang: error reporting and diagnostics, completion
    editorconfig-core-c: set formatting options project-wide
    git: display and cycle through hunks, blame lines, handle file status [installed]
    kak-lsp: Language Server Protocol (LSP) client
    tmux: split windows, spawn tabs
    xdotool: X11 window management
    xorg-xmessage: print detailed crash information in a separate window
( 6/25) installing cronie                           [###########################] 100%
Optional dependencies for cronie
    smtp-server: send job output via email
    smtp-forwarder: forward job output to email server
( 7/25) installing java-runtime-common              [###########################] 100%
For the complete set of Java binaries to be available in your PATH,
you need to re-login or source /etc/profile.d/jre.sh
Please note that this package does not support forcing JAVA_HOME as former package java-common did
( 8/25) installing nspr                             [###########################] 100%
( 9/25) installing nss                              [###########################] 100%
(10/25) installing libjpeg-turbo                    [###########################] 100%
Optional dependencies for libjpeg-turbo
    java-runtime>11: for TurboJPEG Java wrapper [pending]
(11/25) installing jbigkit                          [###########################] 100%
(12/25) installing libtiff                          [###########################] 100%
Optional dependencies for libtiff
    freeglut: for using tiffgt
(13/25) installing lcms2                            [###########################] 100%
(14/25) installing libnet                           [###########################] 100%
(15/25) installing libpng                           [###########################] 100%
(16/25) installing graphite                         [###########################] 100%
Optional dependencies for graphite
    graphite-docs: Documentation
(17/25) installing harfbuzz                         [###########################] 100%
Optional dependencies for harfbuzz
    harfbuzz-utils: utilities
(18/25) installing freetype2                        [###########################] 100%
(19/25) installing java-environment-common          [###########################] 100%
(20/25) installing hicolor-icon-theme               [###########################] 100%
(21/25) installing giflib                           [###########################] 100%
(22/25) installing jdk-openjdk                      [###########################] 100%
Optional dependencies for jdk-openjdk
    java-rhino: for some JavaScript support
    alsa-lib: for basic sound support
    gtk2: for the Gtk+ 2 look and feel - desktop usage
    gtk3: for the Gtk+ 3 look and feel - desktop usage
(23/25) installing wget                             [###########################] 100%
Optional dependencies for wget
    ca-certificates: HTTPS downloads [installed]
(24/25) installing zip                              [###########################] 100%
(25/25) installing unzip                            [###########################] 100%
:: Running post-transaction hooks...
(1/5) Creating system user accounts...
(2/5) Reloading system manager configuration...
(3/5) Arming ConditionNeedsUpdate...
(4/5) Warn about old perl modules
(5/5) Updating the info directory file...
[root@demo ~]#

Installing Tools

Now it is time to clone and install some useful tools. There are three tools in total:

  • Minecraft-Scripts, my collection of Minecraft scripts and config files
  • mcrcon, an RCON client for Minecraft servers
  • Verifier, my file verification utility which is useful for backups

mcrcon

Clone the repository using git clone https://github.com/Tiiffi/mcrcon.git. Enter the downloaded directory with cd mcrcon. We will need to install some tools to build this repository. Use pacman -S base-devel to install some basic developer tools including gcc and make. Run the make command to compile mcrcon. If there are no errors, you should see a file called mcrcon in the current directory. Run make install to install it globally on the system.

Here is that same sequence of commands in an easy to copy/paste format:

git clone https://github.com/Tiiffi/mcrcon.git
cd mcrcon
pacman -S base-devel
make
make install
cd
mcrcon

Here is my terminal session for the entire setup process:

[root@demo ~]# git clone https://github.com/Tiiffi/mcrcon.git
Cloning into 'mcrcon'...
remote: Enumerating objects: 527, done.
remote: Counting objects: 100% (36/36), done.
remote: Compressing objects: 100% (24/24), done.
remote: Total 527 (delta 19), reused 20 (delta 12), pack-reused 491
Receiving objects: 100% (527/527), 116.04 KiB | 1.66 MiB/s, done.
Resolving deltas: 100% (311/311), done.
[root@demo ~]# cd mcrcon 
[root@demo mcrcon]# ls
CHANGELOG.md  INSTALL.md  LICENSE  Makefile  mcrcon.1  mcrcon.c  README.md
[root@demo mcrcon]# pacman -S base-devel 
resolving dependencies...
looking for conflicting packages...

Packages (19) autoconf-2.71-4  automake-1.16.5-2  binutils-2.41-3  bison-3.8.2-6
              debugedit-5.0-5  fakeroot-1.32.2-1  flex-2.6.4-5  gc-8.2.4-1
              gcc-13.2.1-3  guile-3.0.9-1  jansson-2.14-2  libisl-0.26-1
              libmpc-1.3.1-1  libtool-2.4.7+4+g1ec8fa28-6  m4-1.4.19-3  make-4.4.1-2
              patch-2.7.6-10  pkgconf-1.8.1-1  base-devel-1-1

Total Download Size:    67.45 MiB
Total Installed Size:  297.01 MiB

:: Proceed with installation? [Y/n] y
:: Retrieving packages...
 gcc-13.2.1-3-x86_64     46.8 MiB  2.86 MiB/s 00:16 [###########################] 100%
 guile-3.0.9-1-x86_64     8.1 MiB  3.14 MiB/s 00:03 [###########################] 100%
 binutils-2.41-3-x...     7.6 MiB  3.07 MiB/s 00:02 [###########################] 100%
 libisl-0.26-1-x86_64   873.3 KiB  1736 KiB/s 00:01 [###########################] 100%
 bison-3.8.2-6-x86_64   772.5 KiB  1545 KiB/s 00:01 [###########################] 100%
 autoconf-2.71-4-any    644.8 KiB  1384 KiB/s 00:00 [###########################] 100%
 automake-1.16.5-2-any  612.8 KiB  1277 KiB/s 00:00 [###########################] 100%
 make-4.4.1-2-x86_64    523.8 KiB  1290 KiB/s 00:00 [###########################] 100%
 libtool-2.4.7+4+g...   412.4 KiB  1165 KiB/s 00:00 [###########################] 100%
 flex-2.6.4-5-x86_64    307.5 KiB  1015 KiB/s 00:00 [###########################] 100%
 m4-1.4.19-3-x86_64     246.0 KiB   869 KiB/s 00:00 [###########################] 100%
 gc-8.2.4-1-x86_64      234.3 KiB   817 KiB/s 00:00 [###########################] 100%
 patch-2.7.6-10-x86_64   93.0 KiB   458 KiB/s 00:00 [###########################] 100%
 libmpc-1.3.1-1-x86_64   84.2 KiB   413 KiB/s 00:00 [###########################] 100%
 fakeroot-1.32.2-1...    76.6 KiB   383 KiB/s 00:00 [###########################] 100%
 pkgconf-1.8.1-1-x...    57.5 KiB   298 KiB/s 00:00 [###########################] 100%
 jansson-2.14-2-x86_64   51.7 KiB   268 KiB/s 00:00 [###########################] 100%
 debugedit-5.0-5-x...    43.5 KiB   229 KiB/s 00:00 [###########################] 100%
 base-devel-1-1-any       2.0 KiB  11.2 KiB/s 00:00 [###########################] 100%
 Total (19/19)           67.4 MiB  2.47 MiB/s 00:27 [###########################] 100%
(19/19) checking keys in keyring                    [###########################] 100%
(19/19) checking package integrity                  [###########################] 100%
(19/19) loading package files                       [###########################] 100%
(19/19) checking for file conflicts                 [###########################] 100%
(19/19) checking available disk space               [###########################] 100%
:: Processing package changes...
( 1/19) installing m4                               [###########################] 100%
( 2/19) installing autoconf                         [###########################] 100%
( 3/19) installing automake                         [###########################] 100%
( 4/19) installing jansson                          [###########################] 100%
( 5/19) installing binutils                         [###########################] 100%
Optional dependencies for binutils
    debuginfod: for debuginfod server/client functionality
( 6/19) installing bison                            [###########################] 100%
( 7/19) installing debugedit                        [###########################] 100%
( 8/19) installing fakeroot                         [###########################] 100%
( 9/19) installing flex                             [###########################] 100%
(10/19) installing libmpc                           [###########################] 100%
(11/19) installing libisl                           [###########################] 100%
(12/19) installing gcc                              [###########################] 100%
Optional dependencies for gcc
    lib32-gcc-libs: for generating code for 32-bit ABI
(13/19) installing libtool                          [###########################] 100%
(14/19) installing gc                               [###########################] 100%
(15/19) installing guile                            [###########################] 100%
(16/19) installing make                             [###########################] 100%
(17/19) installing patch                            [###########################] 100%
Optional dependencies for patch
    ed: for patch -e functionality
(18/19) installing pkgconf                          [###########################] 100%
(19/19) installing base-devel                       [###########################] 100%
:: Running post-transaction hooks...
(1/2) Arming ConditionNeedsUpdate...
(2/2) Updating the info directory file...
[root@demo mcrcon]# make
gcc -std=gnu99 -Wall -Wextra -Wpedantic -Os -s -fstack-protector-strong -o mcrcon mcrcon.c 
[root@demo mcrcon]# ls
CHANGELOG.md  INSTALL.md  LICENSE  Makefile  mcrcon  mcrcon.1  mcrcon.c  README.md
[root@demo mcrcon]# make install
install -vD mcrcon /usr/local/bin/mcrcon
'mcrcon' -> '/usr/local/bin/mcrcon'
install -vD -m 0644 mcrcon.1 /usr/local/share/man/man1/mcrcon.1
install: creating directory '/usr/local/share/man/man1'
'mcrcon.1' -> '/usr/local/share/man/man1/mcrcon.1'
\nmcrcon installed. Run 'make uninstall' if you want to uninstall.\n
[root@demo mcrcon]# cd
[root@demo ~]# mcrcon
You must give password (-p password).
Try 'mcrcon -h' or 'man mcrcon' for help.
[root@demo ~]#

Verifier

Return to the home directory using cd with no arguments. Run git clone https://gitlab.com/BitiTiger/verifier.git to download the program. Enter the directory with cd verifier. Run the install script using ./install. You can now return to the home directory with cd and verify the installation using verifier --help. If you encounter errors, ensure the python package is installed.

Here is that same sequence of commands in an easy to copy/paste format:

git clone https://gitlab.com/BitiTiger/verifier.git
cd verifier
./install
cd
verifier --help

Here is my terminal session for the entire setup process:

[root@demo ~]# git clone https://gitlab.com/BitiTiger/verifier.git
Cloning into 'verifier'...
remote: Enumerating objects: 348, done.
remote: Counting objects: 100% (15/15), done.
remote: Compressing objects: 100% (13/13), done.
remote: Total 348 (delta 5), reused 5 (delta 2), pack-reused 333
Receiving objects: 100% (348/348), 100.46 KiB | 5.91 MiB/s, done.
Resolving deltas: 100% (197/197), done.
[root@demo ~]# cd verifier/
[root@demo verifier]# ./install 
Getting sudo access...
Making folder for program files...
Copying program files...
Creating systemlink...
DONE! Try "verifier --help" to verify installation
[root@demo verifier]# cd
[root@demo ~]# verifier --help
Usage: verifier [option]
All options:
    --generate  : generate verification metadata
    --verify    : check files for corruption
    --delete    : delete verification metadata
    --help      : show help and exit
    --license   : show program license
    --license-w : show license warranty
    --license-c : show license conditions
    --version   : show version and exit
[root@demo ~]# 

Minecraft-Scripts

Clone the repository using git clone https://gitlab.com/BitiTiger/minecraft-scripts.git. This repository contains most of the files we will need to make in order to use the server. The process is very hands-on so there is no magic script to set it up.

Enter the minecraft-scripts directory using cd minecraft-scripts and run ls to list its contents. To explain what you are looking at, consult the table below. I will guide you through the setup process so try not to worry about these files.

File NameDescription
backupthe automatic backup script used by cron
crontab.txtthe list of cron jobs to run
LICENSE.mdlicense information for the repository
minecraft.servicethe systemd service file for the server
rconthe RCON connection script used by cron
README.mdimportant information for the repository
starta script for manually starting the server
start_servicea script for starting the server via systemd

Note: start will loop forever and is useful when hosting the server via an interactive session through something like screen or tmux. start_service is intended for use with an init system or single-use launches.

Copy the backup, crontab.txt and rcon files to the home directory of the root user with cp backup crontab.txt rcon /root/.

Now let's create the minecraft user which will run the server process. Use useradd -m minecraft to add the user to the system. Copy start, start_service, and minecraft.service to the home directory of the minecraft user with cp start start_service minecraft.service /home/minecraft/. Ensure the files are owned by the minecraft user by running chown minecraft:minecraft /home/minecraft/start /home/minecraft/start_service /home/minecraft/minecraft.service.

Here are all the commands up to this point in an easy to copy/paste format:

git clone https://gitlab.com/BitiTiger/minecraft-scripts.git
cd minecraft-scripts
ls
cp backup crontab.txt rcon /root/
useradd -m minecraft
cp start start_service minecraft.service /home/minecraft/
chown minecraft:minecraft /home/minecraft/start /home/minecraft/start_service /home/minecraft/minecraft.service

Minecraft User Setup

The next part of setup is done from the minecraft user. Use su minecraft and cd to switch to the minecraft user and its home directory. Make a directory called server-files which will contain all the Minecraft related files using mkdir server-files. Enter that directory with cd server-files. Now we will make two directories: one for automatic backups and one for the server jar. Run mkdir autobackups myserver to make the two directories. Move the start and start_service files into the myserver directory with mv /home/minecraft/start /home/minecraft/start_service /home/minecraft/server-files/myserver/.

It is time to download the server jar. Visit the official download page and copy the link to the server jar. Now ensure you are inside the myserver directory with cd /home/minecraft/server-files/myserver and run wget link where link is your copied link. At the time of writing, Minecraft 1.20.2 is the latest version so my command is wget https://piston-data.mojang.com/v1/objects/5b868151bd02b41319f54c8d4061b8cae84e665c/server.jar

Here are all the commands used since the last code block:

su minecraft
cd
mkdir server-files
cd server-files
mkdir autobackups myserver
cd myserver
mv /home/minecraft/start /home/minecraft/start_service /home/minecraft/server-files/myserver/
wget https://piston-data.mojang.com/v1/objects/5b868151bd02b41319f54c8d4061b8cae84e665c/server.jar

Now let's configure the start scripts. Open start in your preferred text editor. Change JAVA_EXECUTABLE="/usr/lib/jvm/java-21-openjdk/bin/java" to the path to your installed version of Java. The path should be similar in the future but with a different number (ex: java-21-openjdk -> java-22-openjdk). Change SERVER_JAR="./server.jar" to the path of your server jar file. This should be the same unless you downloaded a custom server jar. Optionally, you can change JAVA_FLAGS="-Xms2G -Xmx4G" to your preferred memory values. Make sure you update the same lines inside start_service as well. You can use diff to check if the lines match. Here is the output of diff start start_service when the configuration lines match:

[minecraft@demo myserver]$ diff start start_service 
14,32c14,15
< # function which counts down for 10 seconds
< function countdown {
<     for x in {10..1}; do
<         if [ $x -eq 1 ]; then
<             printf "\rRestarting server in $x second... "
<         else
<             printf "\rRestarting server in $x seconds... "
<       fi
<       sleep 1
<     done
<     printf "\rRestarting server now...         \n"
< }
< 
< # keep restarting the server in case it crashes
< while :; do
< 	$JAVA_EXECUTABLE $JAVA_FLAGS -jar $SERVER_JAR $JAR_FLAGS
< 	echo "Server stopped."
< 	countdown
< done
---
> # run the server
> $JAVA_EXECUTABLE $JAVA_FLAGS -jar $SERVER_JAR $JAR_FLAGS

Run the server with ./start_service and wait until it crashes. You should something like this in your terminal:

[00:07:11] [ServerMain/WARN]: Failed to load eula.txt
[00:07:11] [ServerMain/INFO]: You need to agree to the EULA in order to run the server. Go to eula.txt for more info.
Server stopped.

Open eula.txt in your preferred text editor and change eula=false to eula=true. Restart the Minecraft server with ./start_service. The server should output a line stating that the server has loaded like the one below:

[00:09:56] [Server thread/INFO]: Time elapsed: 30943 ms
[00:09:56] [Server thread/INFO]: Done (36.580s)! For help, type "help"

Use the stop command to stop the server. The server may take a long time to stop since it is saving the world for the first time. Open server.properties with your preferred text editor. Change enable-rcon=false to enable-rcon=true and rcon.password= to rcon.password=PASS where PASS is your preferred password. Note that we did not forward the RCON port to the host's network interface so your password doesn't need to be super secure. For this tutorial, I'm using rawrxd as my RCON password so my line is set to rcon.password=rawrxd. I also changed server-port=25565 to server-port=25566 and query.port=25565 to query.port=25566 to match my port forwarding as mentioned earlier. Go ahead and run ./start_service again and verify your server starts up correctly. You should see something similar to the lines below:

[00:17:29] [Server thread/INFO]: Time elapsed: 4830 ms
[00:17:29] [Server thread/INFO]: Done (6.819s)! For help, type "help"
[00:17:29] [Server thread/INFO]: Starting remote control listener
[00:17:29] [Server thread/INFO]: Thread RCON Listener started
[00:17:29] [Server thread/INFO]: RCON running on 0.0.0.0:25575

Congratulations! The Minecraft server part of the setup is complete. Now we just need to configure automatic backups and the system service. Stop the server by using the stop console command. This is the last time we will directly run the start_service script.

Go back to the minecraft user's home directory with cd. Open minecraft.service with your preferred text editor. Change WorkingDirectory=/home/minecraft/server-files/my-server/ to WorkingDirectory=/home/minecraft/server-files/myserver/ . Change ExecStart=/home/minecraft/server-files/my-server/start_service to ExecStart=/home/minecraft/server-files/myserver/start_service.

Systemd Service Configuration

Now we are done with the minecraft user. Run exit to return to a root shell. Ensure you are in root's home directory by running cd.

Link the minecraft.service file to /etc/systemd/system/minecraft.service with ln -s /home/minecraft/minecraft.service /etc/systemd/system/minecraft.service. Reload the systemd daemon using systemctl daemon-reload. This tells systemd that the service files have changed and that it needs to scan for changes. Now use systemctl enable --now minecraft.service to start and enable the Minecraft service. You can use systemctl status minecraft.service to verify the Minecraft server is running. You should see a line that says Active: active (running) in the output. If you are stuck inside a pager window, use q to exit.

[root@demo ~]# systemctl daemon-reload
[root@demo ~]# systemctl enable --now minecraft.service
Created symlink /etc/systemd/system/multi-user.target.wants/minecraft.service → /home/minecraft/minecraft.service.
[root@demo ~]# systemctl status minecraft.service
WARNING: terminal is not fully functional
Press RETURN to continue 
● minecraft.service - Minecraft server
     Loaded: loaded (/etc/systemd/system/minecraft.service; enabled; preset: disabled)
    Drop-In: /run/systemd/system/service.d
             └─zzz-lxc-service.conf
     Active: active (running) since Sat 2023-11-18 00:42:06 UTC; 24s ago
   Main PID: 3252 (start_service)
      Tasks: 52 (limit: 4915)
     Memory: 1.7G
        CPU: 1min 24.901s
     CGroup: /system.slice/minecraft.service
             ├─3252 /bin/sh /home/minecraft/server-files/myserver/start_service
             └─3253 /usr/lib/jvm/java-21-openjdk/bin/java -Xms2G -Xmx4G -jar ./server>

Nov 18 00:42:23 demo start_service[3253]: [00:42:23] [Worker-Main-4/INFO]: Preparing >
Nov 18 00:42:23 demo start_service[3253]: [00:42:23] [Worker-Main-4/INFO]: Preparing >
Nov 18 00:42:23 demo start_service[3253]: [00:42:23] [Worker-Main-11/INFO]: Preparing>
Nov 18 00:42:24 demo start_service[3253]: [00:42:24] [Worker-Main-4/INFO]: Preparing >
Nov 18 00:42:24 demo start_service[3253]: [00:42:24] [Worker-Main-6/INFO]: Preparing >
Nov 18 00:42:25 demo start_service[3253]: [00:42:25] [Server thread/INFO]: Time elaps>
Nov 18 00:42:25 demo start_service[3253]: [00:42:25] [Server thread/INFO]: Done (6.24>
Nov 18 00:42:25 demo start_service[3253]: [00:42:25] [Server thread/INFO]: Starting r>
Nov 18 00:42:25 demo start_service[3253]: [00:42:25] [Server thread/INFO]: Thread RCO>
Nov 18 00:42:25 demo start_service[3253]: [00:42:25] [Server thread/INFO]: RCON runni>
[root@demo ~]#

RCON Configuration

Open /root/rcon in your preferred text editor. Change RCON_PASS="rawrxd" to your RCON password. I set up my server to use rawrxd as my RCON password so no change is needed for me. You can use ./rcon list to verify that the rcon script is working properly.

[root@demo ~]# ./rcon list
There are 0 of a max of 20 players online: 
[root@demo ~]#

Backup Configuration

Open backup in your preferred text editor. Change SERVER_DIR_NAME="my-server" to SERVER_DIR_NAME="myserver". If you followed my tutorial exactly, no other lines should need changes. Test the backup script by running ./backup. If the script does not crash, you have successfully configured backups.

Cron Configuration

Ensure the cronie service is enabled and started by using systemctl enable --now cronie. You can verify it is working by running systemctl status cronie. You should see a line that says Active: active (running) in the output. If you are stuck inside a pager window, use q to exit.

[root@demo ~]# systemctl status cronie
● cronie.service - Command Scheduler
     Loaded: loaded (/usr/lib/systemd/system/cronie.service; enabled; preset: disable>
    Drop-In: /run/systemd/system/service.d
             └─zzz-lxc-service.conf
     Active: active (running) since Sat 2023-11-18 01:05:24 UTC; 44s ago
   Main PID: 4241 (crond)
      Tasks: 1 (limit: 4915)
     Memory: 888.0K
        CPU: 2ms
     CGroup: /system.slice/cronie.service
             └─4241 /usr/sbin/crond -n

Nov 18 01:05:24 demo systemd[1]: Started Command Scheduler.
Nov 18 01:05:24 demo crond[4241]: (CRON) STARTUP (1.7.0)
Nov 18 01:05:24 demo crond[4241]: (CRON) INFO (Syslog will be used instead of sendmai>
Nov 18 01:05:24 demo crond[4241]: (CRON) INFO (RANDOM_DELAY will be scaled with facto>
Nov 18 01:05:24 demo crond[4241]: (CRON) INFO (running with inotify support)
[root@demo ~]#

Now we need to configure the system crontab. Copy the text from crontab.txt. Run EDITOR=kak crontab -e where EDITOR is set to your preferred text editor. Paste the contents of crontab.txt into the editor window. Save and exit.

NOTE: Kakoune can easily paste the contents of another file. From command mode (the default), use | to open the pipe prompt. Type cat crontab.txt and press ENTER. Your crontab file should now match crontab.txt. Use :wq to save and exit Kakoune.

Use crontab -l to verify the crontab saved correctly. My terminal session for this section looks like this:

[root@demo ~]# EDITOR=kak crontab -e
no crontab for root - using an empty one
crontab: installing new crontab
[root@demo ~]# crontab -l
# +--------------- Minute (0-59)
# |  +------------ Hour (0-23)
# |  |  +--------- Day of the month (1-31)
# |  |  |  +------ Month of the year (1-12)
# |  |  |  |  +--- Day of the week (0-6)
# |  |  |  |  |
 55 23  *  *  * /root/rcon "tellraw @a [{\"text\":\"Server will restart in \",\"color\":\"yellow\"},{\"text\":\"5\",\"color\":\"aqua\"},{\"text\":\" minutes.\",\"color\":\"yellow\"}]"
 56 23  *  *  * /root/rcon "tellraw @a [{\"text\":\"Server will restart in \",\"color\":\"yellow\"},{\"text\":\"4\",\"color\":\"aqua\"},{\"text\":\" minutes.\",\"color\":\"yellow\"}]"
 57 23  *  *  * /root/rcon "tellraw @a [{\"text\":\"Server will restart in \",\"color\":\"yellow\"},{\"text\":\"3\",\"color\":\"aqua\"},{\"text\":\" minutes.\",\"color\":\"yellow\"}]"
 58 23  *  *  * /root/rcon "tellraw @a [{\"text\":\"Server will restart in \",\"color\":\"yellow\"},{\"text\":\"2\",\"color\":\"aqua\"},{\"text\":\" minutes.\",\"color\":\"yellow\"}]"
 59 23  *  *  * /root/rcon "tellraw @a [{\"text\":\"Server will restart in \",\"color\":\"yellow\"},{\"text\":\"1\",\"color\":\"aqua\"},{\"text\":\" minute.\",\"color\":\"yellow\"}]"
  0  0  *  *  * /root/backup | tee -a /root/autobackup.log
[root@demo ~]#

Timezone Configuration (optional)

By default, the provided crontab will display notices and perform an automatic backup at midnight in the system timezone. Most Linux servers use UTC by default. If you would like to align the crontab schedule with your local timezone, follow these steps.

Run timedatectl list-timezones and locate your timezone from the list. For me, my local timezone is called America/New_York. Use timedatectl set-timezone my_timezone where my_timezone is your local timezone from the list. To set your timezone to America/New_York, use timedatectl set-timezone America/New_York. Verify your timezone is set correctly by running date.

[root@demo ~]# date
Sat Nov 18 01:20:58 AM UTC 2023
[root@demo ~]# timedatectl list-timezones | grep -i new
America/New_York
America/North_Dakota/New_Salem
Canada/Newfoundland
[root@demo ~]# timedatectl set-timezone America/New_York
[root@demo ~]# date
Fri Nov 17 08:21:14 PM EST 2023
[root@demo ~]#

Final Testing

Congratulations! We should be done now. We can verify everything is set up by restarting the container and checking that everything starts up properly.

Exit the container by running exit. Restart the container using lxc restart demo. Launch a shell inside the container with lxc shell demo. Now you can check the output of ps aux and verify that java and crond are running. The output of crontab -l should match the crontab settings you had prior to restarting the container. Running ./rcon list should list the players on the Minecraft server without error. Here is my terminal session for verifying setup.

[root@demo ~]# exit
logout
cait@athena ~> lxc restart demo
cait@athena ~> lxc shell demo
[root@demo ~]# ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.1  21136 12928 ?        Ss   20:24   0:00 /sbin/init
root       119  0.0  0.1 131784 18228 ?        Ss   20:24   0:00 /usr/lib/systemd/syst
root       127  0.0  0.0  16752  6784 ?        Ss   20:24   0:00 /usr/lib/systemd/syst
root       152  0.0  0.0  29844  8832 ?        Ss   20:24   0:00 /usr/lib/systemd/syst
systemd+   170  0.0  0.1  21256 13184 ?        Ss   20:24   0:00 /usr/lib/systemd/syst
dbus       178  0.0  0.0   8808  4736 ?        Ss   20:24   0:00 /usr/bin/dbus-daemon 
root       179  0.0  0.0  17024  7808 ?        Ss   20:24   0:00 /usr/lib/systemd/syst
root       180  0.0  0.0  17192  7936 ?        Ss   20:24   0:00 /usr/lib/systemd/syst
systemd+   185  0.0  0.0  18576  9472 ?        Ss   20:24   0:00 /usr/lib/systemd/syst
minecra+   186  0.0  0.0   7512  3712 ?        Ss   20:24   0:00 /bin/sh /home/minecra
root       201  0.0  0.0  10892  7296 ?        Ss   20:24   0:00 sshd: /usr/bin/sshd -
root       208  0.0  0.0   5496  2176 pts/0    Ss+  20:24   0:00 /sbin/agetty -o -p --
root       209  0.0  0.0   6664  3200 ?        Ss   20:24   0:00 /usr/sbin/crond -n
minecra+   213  0.0 15.8 9349672 1926716 ?     Sl   20:24   2:21 /usr/lib/jvm/java-21-
root       296  0.0  0.0  17140  6528 ?        S    20:29   0:00 systemd-userwork
root       297  0.0  0.0  17140  6528 ?        S    20:29   0:00 systemd-userwork
root       298  0.0  0.0  17140  6528 ?        S    20:29   0:00 systemd-userwork
root       301  0.0  0.0  10412  4224 pts/1    Ss   20:33   0:00 su -l
root       302  0.0  0.0   7776  4480 pts/1    S    20:33   0:00 -bash
root       306  0.0  0.0  10992  4352 pts/1    R+   20:33   0:00 ps aux
[root@demo ~]# crontab -l
# +--------------- Minute (0-59)
# |  +------------ Hour (0-23)
# |  |  +--------- Day of the month (1-31)
# |  |  |  +------ Month of the year (1-12)
# |  |  |  |  +--- Day of the week (0-6)
# |  |  |  |  |
 55 23  *  *  * /root/rcon "tellraw @a [{\"text\":\"Server will restart in \",\"color\":\"yellow\"},{\"text\":\"5\",\"color\":\"aqua\"},{\"text\":\" minutes.\",\"color\":\"yellow\"}]"
 56 23  *  *  * /root/rcon "tellraw @a [{\"text\":\"Server will restart in \",\"color\":\"yellow\"},{\"text\":\"4\",\"color\":\"aqua\"},{\"text\":\" minutes.\",\"color\":\"yellow\"}]"
 57 23  *  *  * /root/rcon "tellraw @a [{\"text\":\"Server will restart in \",\"color\":\"yellow\"},{\"text\":\"3\",\"color\":\"aqua\"},{\"text\":\" minutes.\",\"color\":\"yellow\"}]"
 58 23  *  *  * /root/rcon "tellraw @a [{\"text\":\"Server will restart in \",\"color\":\"yellow\"},{\"text\":\"2\",\"color\":\"aqua\"},{\"text\":\" minutes.\",\"color\":\"yellow\"}]"
 59 23  *  *  * /root/rcon "tellraw @a [{\"text\":\"Server will restart in \",\"color\":\"yellow\"},{\"text\":\"1\",\"color\":\"aqua\"},{\"text\":\" minute.\",\"color\":\"yellow\"}]"
  0  0  *  *  * /root/backup | tee -a /root/autobackup.log
[root@demo ~]# ./rcon list
There are 0 of a max of 20 players online: 
[root@demo ~]#

Now it is time to play on the server and verify it works. Open Minecraft and click on Multiplayer. Click Direct Connection and type the IP address and port of the server. The format is address:port. For me, this would be 192.168.0.3:25566

Credits

All of this information comes from my own personal experience except for the mcrcon build instructions. However, there are many great resources for learning more.