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*.jar file 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 either peaceful, easy, normal or hard
  • enforce-whitelist: If the server is publically accessible, this must be true so no unwantes strangers enter your world
  • gamemode: Depending on what your server is meant to be, this can be survival, creative, adventureor spectator
  • level-name: This ist the name of your world
  • level-seed: You can set a pre-selected seed if you want but it will be randomized if you let it blank
  • max-players: Define the maximum simultaneous player count - depends on your server hardware
  • motd The displayed Server text in the connection dialog of clients
  • online-mode: Set this true to verify accounts against the Microsoft server, only set this to false if 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 to true to 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

This page was last edited on 2026-01-02 15:52

Powered by Wiki|Docs

This page was last edited on 2026-01-02 15:52

Bjørn Singer
Private wiki!

Powered by Wiki|Docs