Hosting a dedicated Arma 3 server on Ubuntu

The penguin way to the battlefield

Sebastian Wirkijowski
10 min readJun 5, 2023

--

So you want to set up a server for you and your mates, but don’t really know how to get around to it? You’ve come to the right place then.

To quickly summarize what this guide is about: we’re going to set up an Arma 3 server that’s going to be controlled with a systemd service, and accessible via the screen utility. This configuration will support multiple mod packs, allowing you to easily choose presets without using any extra tools. With this setup, everyone can use mods downloaded directly from the Steam Workshop, without utilities like Arma3Sync.

Yes, you can just copy the commands and file contents from the code blocks and paste them in the same sequance as they appear without actually reading anything.

Tested on Ubuntu 22.04.2 LTS.

Things to consider

As a good security practice and Valve’s completely reasonable recommendation, you should make a separate Steam account for your dedicated server.

Infrastructure

The most important thing for an Arma 3 server is the CPU, focus on highest speeds instead of number of cores/threads. There shouldn’t be any noticeable difference between using an SSD vs. HDD. Virtual servers should prove perfectly capable for small groups up to about 16 players.

If you’re just starting out your community or need a small server for weekend operations with your friends, I highly recommend Hetzner as your server provider due to ease of configuration, good pricing and amount of options. You can use my referral link to get €⁠20 in cloud credits to play around with.

Mods

Linux uses a case-sensitive file system, which means that some mods will not function properly if loaded without any adjustments. Fortunately for us, we can modify the files and directory names enough to fix this without breaking the mods or players having to modify anything on their end.

There are two approaches to fixing that problem.

  1. Renaming the files to make all files and directories lowercase using a script. That’s the easy and simple way.
  2. Setting up an overlay file system (ciopfs — case insensitive on purpose filesystem), which I sadly won’t cover this time.

Getting started

Let’s start by installing the SteamCMD and screen packages while still in a privileged user, then — as a security best practice — creating a separate user for the steam client. You’ll be prompted to accept the EULA on installation.

apt install steamcmd screen && useradd -m steam

Remember that you can always use man to get help about any command. For example: man screen.

The prompt above will install the package, then — on success (&&) — create a user named steam with its own home directory (-m).

Now, we can prepare our directory structure. Following the FHS 3.0, we’re gonna put our game server in the /srv directory. The most important part is setting the owner (chown) and permissions (chmod) of the folder, so that the game server installation process won’t fail.

mkdir /srv/arma3server && \
chown steam:steam /srv/arma3server -Rv && \
chmod u+rwx /srv/arma3server -Rv

The -R parameter applies changes to files and directories recursively, while the -v parameter prints out every action that is performed.

Installing the game server

With that out of the way, it’s time to switch to our steam user and install the Arma 3 Dedicated Server (AppID 233780). It’s important to switch to the home directory before using the steamcmd command.

su steam
cd ~ && steamcmd

The lone ~ character always resolves to the current user home directory.

You’re now in the SteamCMD interface. The following process is the same for installing and updating the game server. Enter each command separately in the specified sequence.

// Select installation directory
force_install_dir /srv/arma3server

// Log in to your user
login your_steam_user

// Download the server
app_update 233780 validate

// Exit SteamCMD
exit

Remember to always use the force_install_dir command before logging in. If for any reason the download process fails, can use the app_update command again to resume.

The last step in the installation is creating the directories used to store profile files.

mkdir -p ~/".local/share/Arma 3" && mkdir -p ~/".local/share/Arma 3 - Other Profiles"

We’re done with the steam user for now, so let’s exit back to our privileged user and continue our journey.

exit

Configuring the game server

Arma 3 server has two configuration files:

  1. Server Config File server.cfg, determined by -config option.
  2. Basic Server Config File, aka “Network Config File” — network.cfg, determined by -cfg option. It’s best not to touch it unless you know what you’re doing, so we’re going to skip it.

You can name your configuration files however you want, as long as you use the same name in the launch options of the server.

Server Config

Switch to the game server directory and create the config file using your editor of preference. I’ll be using nano, since it’s pretty simple to use.

cd /srv/arma3server && nano server.cfg

Below is an example server.cfg file, feel free to paste it. Consult the documentation for additional configuration. You do not have to define variables you’re planning to use default settings for.

// General
hostname = "My Arma 3 Server";
password = "Super strong but reasonable length password";
passwordAdmin = "Stronger password"
maxPlayers = 16;

admins[] = {"Your UID", "Another UID"};

loopback = false;
upnp = false;

// Logging and diagnostics
logFile = "server.log";
enablePlayerDiag = 1;
statisticsEnabled = 1;

// Security
BattlEye = 1;
verifySignatures = 2;
allowedFilePatching = 0;
allowedLoadFileExtensions[] = { "hpp","sqs","sqf","fsm","cpp","paa","txt","xml","inc","ext","sqm","ods","fxy","lip","csv","kb","bik","bikb","html","htm","biedi" };
allowedPreprocessFileExtensions[] = { "hpp","sqs","sqf","fsm","cpp","paa","txt","xml","inc","ext","sqm","ods","fxy","lip","csv","kb","bik","bikb","html","htm","biedi" };
allowedHTMLLoadExtensions[] = { "htm","html","xml","txt" };
kickDuplicate = 1;

// Ingame Settings
persistent = 0;
vonCodec = 1;
drawingInMap = 1;
skipLobby = false;
allowProfileGlasses = true;

// Mission Cycle
autoSelectMission = true;
randomMissionOrder = true;

Setting up mods

The way we’ll handle mods in this guide is a bit different. The weird thing about how Arma is handling mod loading is that you have to specify each and every add-on in a list — -mods=@CBA_A3;@ace;@enhanced_movement. You can manually list all the add-ons, but why bother if you can automate it! We’re going to do scripts for that next, but now we have to prepare our mods.

Create a directory for our main/default mod preset.

mkdir /srv/arma3server/mods/default 

You’re gonna place all your desired add-ons in this folder. If you want to make more presets, feel free to create extra directories directly in mods/. Remember to use lowercase and avoid special characters in the directory names.

If you want to add any server mods (-servermod=), create a separate directory for them. You can duplicate the method used for presets and adjust it to load server addons.

Multiple Presets

If you’re going to use the same mods in multiple presets, place them in the default/ directory and use symlinks to them in other directories.

ln -sr /srv/arma3server/mods/default/* /srv/arma3server/mods/other_preset/

The * character is a wildcard, this means that it will link all directories and files directly in mods/. Use the addon directory name instead if you want to link addons one by one.

How to add Creator DLCs to the server?

For that, you’re going to have to switch to the creatordlc beta branch of Arma 3 Dedicated Server.

  1. Repeat the actions from installing the server, but use app_update 233780 -beta creatordlc validate instead of app_update 233780 validate.
  2. Create symlinks from the desired DLC directories in /srv/arma3server to the mods/preset/ folder. The new directories are:
    - vn — S.O.G. Prairie Fire
    - gm — Global Mobilization
    - ws — Western Sahara
    - csla — Iron Curtain
ln -sr /srv/arma3server/vm /srv/arma3server/mods/default/

Management scripts

Make a scripts directory and switch to it.

mkdir /srv/arma3server/scripts && cd /srv/arma3server/scripts

Mod Preparation Script

Remember when I mentioned how some mods may not work on Linux systems out of the box? Yeah, now’s the part where we tackle this problem.

Use your editor of preference to make a prepMods.sh file.

nano prepMods.sh
#!/usr/bin/env bash

serverDirectory="/srv/arma3server/"; # Working directory

find "${serverDirectory}/mods" -depth -exec rename -v 's/(.*)\/([^\/]*)/$1\/\L$2/' {} \;
find "${serverDirectory}/mods" -depth -type d -exec rename -v 's/\ /_/g' {} \;

This script does 2 things:

  1. Changes all files and directories to lowercase.
  2. Replaces white spaces on directories with underscores.

It’s important to run it after adding or updating any mods. You can also make it run every time you start the server by adding ExecStartPre=/srv/arma3server/scripts/mods.sh before the first ExecStartPre in the service unit file we’ll cover later, but it will add some extra time to the spin up sequence, so I don’t recommend it.

Preset Selection Script

Now here’s the different mod handling part. Since we’re using presets, we’re going to have to read the service environment file to get our mod preset, and then select the appropriate folder to get all the mods from.

This script will create a list of all mods in our preset and spit it out as an environment file (/srv/arma3server/mods.conf) ready to be used in our service.

Use your editor of preference to make a mods.sh file.

nano mods.sh
#!/usr/bin/env bash
source /srv/arma3server/service.conf
cd /srv/arma3server

echo "MODS=$(find "mods/${MODLIST}" -maxdepth 1 -mindepth 1 -name "*" | sed -z 's/\n/\;/g')" > /srv/arma3server/mods.conf

Server Stop Script

Use your editor of preference to make a stop.sh file.

Note: You do not have to do this if you’re not planning to add any extra actions to the shutdown procedure, instead you can use the listed commands in sequence using && between them in the service unit file we’ll cover later, in the ExecStop action.

nano stop.sh

#!/usr/bin/env bash

screen -S arma3server -X at "#" stuff '^C'
sleep 5
screen -S arma3server -X quit
screen -wipe

exit 0

This file is supposed to do a clean stop of the server and quit the screen terminal instance. You can have some fun with it, like inserting a countdown sequence to give your players a heads-up before shutting down.

Setting up the service

As the privileged user, create a unit file with your editor of choice. The name you will use for your file will be the name you’ll have to use to manage the service, using service service_name start|stop|status.

nano /etc/systemd/system/arma3server.service

You can use this service file along with my scripts without any modification.

[Unit]
Description=Arma 3 Dedicated Server

[Service]
Type=forking
User=steam
Group=steam
WorkingDirectory=/srv/arma3server

ProtectHome=false
ProtectKernelTunables=true

EnvironmentFile=/srv/arma3server/service.conf
EnvironmentFile=/srv/arma3server/mods.conf

ExecStartPre=/srv/arma3server/scripts/mods.sh
ExecStart=screen -dmS arma3server ./arma3server \
-name=arma3server \
-config="/srv/arma3server/${CONFIG}" \
-cfg="/srv/arma3server/${NETCONFIG}" \
-bepath="/srv/arma3server/${BEPATH}" \
-mod="${MODS}"
ExecStartPost=screen -S arma3server -X multiuser on
ExecStartPost=screen -S arma3server -X acladd root
ExecStop=/srv/arma3server/scripts/stop.sh

KillMode=control-group

Restart=on-failure
RestartSec=60

TimeoutStopSec=30

[Install]
WantedBy=default.target

Some important notes about that unit file:

  1. The environment variables and files (/srv/arma3server/mods.conf) are only accessible to the ExecStart action.
  2. You should set ProtectHome to true if not using screen.
  3. You can remove the ExecStartPost=-rm /srv/arma3server/scripts/restart line if you’re not planning to use my stop.sh and discord webhook scripts.
  4. You can replace the root after acladd with your own user.
  5. In this configuration, the server will try to restart on failure after 60 seconds.

After you’re done, reload the systemctl daemon to load the new unit file.

systemctl daemon-reload

Remember to run this command every time you edit that unit.

Service Environment File

This is our most important file, a special place where we set our game server start parameters. Our unit file uses it to load all the arguments needed for easy configuration of our server without the need to modify the service itself.

Create the service.conf file.

nano /srv/arma3server/service.conf
CONFIG=server.cfg
NETCONFIG=network.cfg
BEPATH=battleye
MODLIST="default"

Here you can select your mod preset by changing the MODLIST variable to the directory name corresponding to your desired preset from mods/.

Firewall configuration

It’s a good practice to always lock down your server and protect it from any bad actors.

Use your preferred firewall tool (I recommend ufw) or hosting provider’s panel to set up the following rules:

  • SSH (if using remote connection) — 22/tcp
  • Arma 3 — 2302–2306/udp
  • BattlEye — 2344/udp&tcp, 2345/udp
  • Steam API — 27015–27050
  • Optional BattlEye RCON — 2301/udp

Don’t forget to enable your firewall afterwards!

Starting the server

Well not yet, as a final touch we’re going to make sure that all of our game server files have correct permissions and owners set.

chown steam:steam /srv/arma3server -R && chmod u+rwx /srv/arma3server -R

Important note: Remember to do this step every time you make changes to the mods. This is the most common problem of servers not working properly or addons not loading.

Congratulations, you did it! If you’ve done everything correctly, you should have an Arma 3 server ready to go.

  • Start the server using service arma3server start
  • Check the status using service arma3server status
  • Stop the server using service arma3server stop
  • Check the console using screen with screen -x steam/arma3server
  • Make the server start automatically with service arma3server enable

Bonus: Hooking up the Discord Webhooks

First create a webhook by editing your desired channel on Discord, then Integrations Create Webhook.

Add the following line to the arma3server.service file under [Service] block:

ExecStartPost=/srv/arma3server/scripts/notif.sh -t start
ExecStopPost=/srv/arma3server/scripts/notif.sh -t stop

Create a notif.sh file in the /srv/arma3server/scripts directory with the following content.

nano /srv/arma3server/scripts/notif.sh
#!/usr/bin/env bash

source /srv/arma3server/service.conf
webhook="your_webhook_url_here"

while getopts t: flag
do
case "${flag}" in
t) type=${OPTARG};;
esac
done

timestamp=$(date +%s)

echo "Sending (${type}) notification";

case $type in
start)
curl -H "Content-Type: application/json" -d "{ \"content\": \"[<t:${timestamp}:R>] **Server is starting!** Using ${MODLIST^} preset\" }" $webhook;;
stop)
curl -H "Content-Type: application/json" -d "{ \"content\": \"[<t:${timestamp}:R>] **Server is offline** Thanks for playing!\" }" $webhook;;
esac

exit 0

It does support embeds, you can really play around with the notifications here.

This will read our service config to get our preset, check what type of notification we want to send by getting provided arguments, generate a timestamp and send out our notification based on the argument.

And that’s it! Let me know if you have any questions, I’ll try to answer them in a reasonable timeframe.

Good luck on the battlefield.

--

--

Sebastian Wirkijowski
0 Followers

Curiosity × Better tomorrow | Consulting ✴ IT Solutions | Full-Stack Developer