MineCraft Server tunneled through SSH
I have a MineCraft server in the local network of my friend's kids for them to play on it without it being accessible from the outside. But I also want to be able to join it from my home to administer it. Unfortunatelly, their internet provider does not support dynamic DNS of any kind (it's IPv6 only and the router is locked down) so that I chose to let the server call my home server and forward the neccessary ports.
- Port
22->10022: SSH back to the server - Port
25565->25565: Minecraft Java - Port
24454->24454: SimpleVoiceChat - Port
19132->19132: Minecraft Bedrock via Geyser
Home Server
The Home Server (HS) must be accessible from the MineCraft Server (MS) via SSH of course. To make this secure, I create a non-privileged user sshporter on the HS which the MS will be able to connect as via an unprotected certificate (one which has no password on it):
adduser sshporter
Make sure your SSH server is secure (pubic key only, no password) and allows port forwarding for the sshporter user:
Include /etc/ssh/sshd_config.d/*.conf
Port 22
PubkeyAuthentication yes
PasswordAuthentication no
AuthorizedKeysFile .ssh/authorized_keys .ssh/authorized_keys2
ChallengeResponseAuthentication no
UsePAM yes
PrintMotd no
X11Forwarding no
AcceptEnv LANG LC_*
Subsystem sftp /usr/lib/openssh/sftp-server
Match User sshporter
GatewayPorts yes
AllowTcpForwarding yes
A simple busy script /home/sshporter/okay.sh. The MS will execute this script via SSH and thus keep the connection alive.
#! /usr/bin/sh
echo Okay
while true; do
sleep 1
done
After you created a private/public key pair on the MS, add the public key to the user's authorised keys /home/sshporter/.ssh/authorized_keys with instructions to execute the okay.sh script on login:
command="/home/sshporter/okay.sh" ssh-ed25519 [YOUR_PUBLIC_KEY] sshporter
MineCraft Server
Make sure we have all requirements:
apt install screen ssh
Make sure the disconnects are detected quickly by setting timeouts. Add the following to ~/.ssh/config where the ~ directory is the user's home directory who will do the connection (root in my case):
Host your.server
Hostname your.server
ServerAliveInterval 6
ServerAliveCountMax 5
Create a new private/public key pair to log into the HS. First command creates the private key, second extracts the public part of it:
ssh-keygen -t ed25519 -C "sshporter" -f ~/.ssh/sshporter
ssh-keygen -y -f ~/.ssh/sshporter > ~/.ssh/sshporter.pub
A simple (re-)connection script /opt/sshporter.sh on the client side:
#! /bin/sh
while true; do
ssh -i /path/to/certificate -g \
-R \*:10022:localhost:22 \
-R \*:25565:localhost:25565 \
-R \*:24454:localhost:24454 \
-R \*:19132:localhost:19132 \
sshporter@your.server \
-p 22
sleep 5
done
Don't forget to make the script executable:
chmod +x /opt/sshporter.sh
Create a service file /opt/sshporter.service to make the server run te connection script automatically:
[Unit]
Description=SSH Porter
After=network.target
[Service]
Type=simple
User=root
ExecStart=/opt/sshporter.sh
Restart=on-failure
[Install]
WantedBy=default.target
Connect to the server manually once to add its key to the known_hosts list:
ssh sshporter@your.server -p 22
Activate the new service:
systemctl enable --now /opt/sshporter.service
Minecraft Server setup
I opted for a Spigot server which is a bit more work than an easy PaperMC installation but it seemed to run faster.
Install requirements
First we need the Java SDK. Make sure not to just install the Java JRE because we will need the Java compiler javac, too. You can just use the default Orical version for Debian. Here are the official downloads. Then you can just install it via apt install /path/to/jdk.deb while actually giving it the path, e.g. with ./jdk.deb.
Also make sure screen and curl are installed as we'll need that to install and handle the server process in our linux deamon to gracefully shut it down: apt install screen
Build your spigot.jar
I followed the instructions here. Here are the main steps:
- Download the Spigot BuildTools:
curl -o BuildTools.jar https://hub.spigotmc.org/jenkins/job/BuildTools/lastSuccessfulBuild/artifact/target/BuildTools.jar - Execute the BuldTools:
java -jar BuildTools.jar - All you need is the
spigot*.jarfile so copy that into your target directory (I chose/opt/spigot) - You can delete the mess the BuildTools left on your harddrive now
Startup the server
You first will need to start the server once (java -jar spigot.jar) and it will sonn stop again. This is because you'll have to agree to the EULA. To do so, edit the created eula.txt file and set eula=true.
Next step is to setup the settings in server.properties which is important as it will set what the server does on first real start-up. What each option does can be read in the MineCraft Wiki. Here are the most important ones:
difficulty: Set to eitherpeaceful,easy,normalorhardenforce-whitelist: If the server is publically accessible, this must betrueso no unwantes strangers enter your worldgamemode: Depending on what your server is meant to be, this can besurvival,creative,adventureorspectatorlevel-name: This ist the name of your worldlevel-seed: You can set a pre-selected seed if you want but it will be randomized if you let it blankmax-players: Define the maximum simultaneous player count - depends on your server hardwaremotdThe displayed Server text in the connection dialog of clientsonline-mode: Set thistrueto verify accounts against the Microsoft server, only set this tofalseif your server has no internet access or you need access for players without an account but keep in mind that everyone can give themselves every username then - even your's!white-list: Set this totrueto enable whilelist management so only registered users can enter your server; otherwise your only option is to allow everybody expect explicitely blacklisted users
You can leave the rest on default.
Now you should be able to initialize your world by starting the server manually:
java -Xms2G -Xmx2G -XX:+UseG1GC -jar spigot.jar nogui
Once the server is up and running, make sure to add your username to the whitelist with whitelist add [YOUR_MINECRAFT_NAME] and give yourself operator privileges with op [YOUR_MINECRAFT_NAME]. Then connect to the server with your minecraft client and check if everything works.
Once that works, stop the server via the stop command in the server console.
As we'll want the server to run as an unprivileged user, we'll create that (mc in my case) and give it access to the spigot folder and everything in it. The --system flag omits the user from loggin in and thus it also doesn't need password.
adduser mc --system
chown -R mc:root /opt/spigot
Adding plugins
To add server plugins, just download them into the plugins folder that has been created and restart the server (you can gracefully shut it down with the stop command in the server console).
You should check the server console by manually starting the server as explained above to see if the plugis are coming up fine or if they are reporting errors.
Background service
As you won't wanna run MineCraft from the console, we'll create a linux deamon that will start the server on boot automativally in the background. For that, we need some scripts first that the deamon can call.
One to start the server and monitor the process id: /opt/spigot/start.sh
#!/bin/bash
PIDFILEPATH=/opt/spigot/minecraft.pid
getScreenPid() {
echo $(screen -ls | awk '/\.minecraft\t/' | cut -d '.' -f 1 | xargs)
}
cd /opt/spigot
# Get current inecraft screen pid
mcpid=$(getScreenPid)
if [[ "$mcpid" != "" && -e /proc/$mcpid ]]; then
echo "Server is already running. PID: $mcpid"
echo $mcpid > "$PIDFILEPATH"
exit 1
elif [[ -e "$PIDFILEPATH" ]]; then
echo "Found old pid file. Deleting..."
rm "$PIDFILEPATH"
fi
# Start the server in a screen process so we can send commands later
screen -dmSL "minecraft" -Logfile "/opt/spigot/logs/screen.log" java -Xms4G -Xmx4G -XX:+UseG1GC -jar spigot.jar nogui
# Wait for the screen process to start
mcpid=$(getScreenPid)
retries=0
while [[ "$mcpid" == "" && $retries -lt 15 ]]; do
sleep 1
mcpid=$(getScreenPid)
retries=$((retries + 1))
done
if [[ "$mcpid" == "" ]]; then
echo "Failed to start!"
exit -1
fi
# Write the process ID to the specified file
echo $mcpid > "$PIDFILEPATH"
# Wait for the process to stop
while [[ -e /prop/$mcpid ]]; do
sleep 1
done
exit 0
One script to gracefully or forcefully shut the server down: /opt/spigot/stop.sh
#!/bin/bash
PIDFILEPATH=/opt/spigot/minecraft.pid
getScreenPid() {
echo $(screen -ls | awk '/\.minecraft\t/' | cut -d '.' -f 1 | xargs)
}
cd /opt/spigot
# Get current inecraft screen pid
mcpid=$(getScreenPid)
if [[ "$mcpid" != "" && -e /proc/$mcpid ]]; then
echo "Stopping server. PID: $mcpid"
screen -r minecraft -X stuff 'stop\n'
retries=0
# Wait for server to stop gracefully for 30 seconds
while [[ "$mcpid" != "" && $retries -lt 30 ]]; do
sleep 1
mcpid=$(getScreenPid)
retries=$((retries + 1))
done
# If task is still running, try to kill it
if [[ "$mcpid" != "" && -e /proc/$mcpid ]]; then
echo "Server hasn't stopped yet. Killing..."
kill -s 9 $mcpid
fi
fi
exit 0
And a last one for convenience to send commands to the server console: /opt/spigot/send.sh:
#!/bin/bash
cd /opt/spigot
screen -r minecraft -X stuff '$1\n'
Make sure, the scripts are executable and are owned by mc:
chown mc:root *.sh
chmod +x *.sh
You should try if the files are running. Use screen or tmux to execute the blocking start.sh in one terminal and stop.sh in another when the server has started up completely.
Then we need a service file: /opt/minecrafr.service
[Unit]
Description=MineCraft Spigot Server
After=network.target
[Service]
PIDFile=/opt/spigot/minecraft.pid
ExecStart=/opt/spigot/start.sh
ExecStop=/opt/spigot/stop.sh
WorkingDirectory=/opt/spigot
Type=forking
Restart=always
User=mc
[Install]
WantedBy=default.target
RequiredBy=network.target
Then enable the service and start it and check the service status:
systemctl enable /opt/minecraft.service
systemctl start minecraft
systemctl status minecraft
journalctl -xeu minecraft