AzerothCore 3.3.5a
AzerothCore 3.3.5a installs on a clean Debian 13 as root with one long command and runs under pm2. No sudo, no separate user, no Docker.
| Component | What it is |
|---|---|
authserver | login, port 3724 |
worldserver | game world, port 8085 |
| MySQL | databases acore_auth, acore_characters, acore_world; user acore |
| pm2 | starts both servers and brings them back after a reboot |
| Client data | dbc, maps, vmaps, mmaps from the prebuilt wowgaming/client-data v20.0 package |
mod-anticheat | optional anti-cheat module |
Requirements#
The heavy part is the build: it needs about 1.5 GB of RAM per compile job. A running server is fine with 2–3 GB.
- OS
- Debian 13, as root
- RAM for the build *
- 4 GB comfortable
- 2 GB with swap and one job
- Build time *
- 15–30 min on 4 cores
- 1–2 h on 1–2 cores
- Running *
- 2–3 GB RAM, 2 vCPU
- for 20–30 players
- Disk *
- 40 GB comfortable
- 25 GB is tight
- Open ports (TCP)
- 3724, 8085
- login, game
- Game client
- 3.3.5a
- on the player's PC
* Estimates, they depend on the machine. Check the real numbers with the commands in Maintenance. The playerbots module needs far more (hundreds of bots want 8+ GB RAM) and is not used here.
Preparation#
Before the long command you need two things: tmux, so a dropped SSH session doesn't kill the build, and the number of compile jobs for your machine.
Install tmux and open a session:
apt update && apt install -y tmux && tmux new -s azerothDetach without stopping the build: Ctrl+B, then D. Come back with:
tmux attach -t azerothThen, inside tmux, work out J: the number of compile jobs. It takes the smaller of your core count and your RAM at about 1.5 GB per job.
J=$(awk -v c=$(nproc) '/MemTotal/{m=int($2/1024/1024*10/15); if(m<1)m=1; if(m>c)m=c; print m}' /proc/meminfo) && echo "J=$J (cores: $(nproc), RAM: $(free -h | awk '/Mem:/{print $2}'))"J only lives in this tmux session. With less than 3 GB of RAM, add swap. This is for a regular VM; in an LXC container swap is set by the Proxmox host.
fallocate -l 4G /swapfile && chmod 600 /swapfile && mkswap /swapfile && swapon /swapfile && echo '/swapfile none swap sw 0 0' >> /etc/fstabInstall, build and start#
The whole install is one chain joined with &&: if any step fails, the rest don't run. Paste it inside tmux after J is set.
export DEBIAN_FRONTEND=noninteractive && V=0.8.36-1 && apt update && apt install -y git cmake make gcc g++ clang libssl-dev libbz2-dev libreadline-dev libncurses-dev libboost-all-dev lsb-release gnupg wget p7zip-full nodejs npm && npm install pm2 -g &&
mkdir -p ~/mysqlpackages && cd ~/mysqlpackages &&
wget "https://dev.mysql.com/get/mysql-apt-config_${V}_all.deb" &&
wget "https://dev.mysql.com/downloads/gpg/?file=mysql-apt-config_${V}_all.deb&p=37" -O mysql-apt-config_${V}_all.deb.asc &&
gpg --keyserver keyserver.ubuntu.com --recv-keys A8D3785C &&
gpg --verify mysql-apt-config_${V}_all.deb.asc mysql-apt-config_${V}_all.deb &&
dpkg -i ./mysql-apt-config_${V}_all.deb && apt update && apt install -y mysql-server libmysqlclient-dev &&
rm -v ~/mysqlpackages/mysql-apt-config_${V}_all* &&
mysql -e "DROP USER IF EXISTS 'acore'@'localhost'; CREATE USER 'acore'@'localhost' IDENTIFIED BY 'acore' WITH MAX_QUERIES_PER_HOUR 0 MAX_CONNECTIONS_PER_HOUR 0 MAX_UPDATES_PER_HOUR 0; CREATE DATABASE IF NOT EXISTS acore_world DEFAULT CHARACTER SET UTF8MB4 COLLATE utf8mb4_unicode_ci; CREATE DATABASE IF NOT EXISTS acore_characters DEFAULT CHARACTER SET UTF8MB4 COLLATE utf8mb4_unicode_ci; CREATE DATABASE IF NOT EXISTS acore_auth DEFAULT CHARACTER SET UTF8MB4 COLLATE utf8mb4_unicode_ci; GRANT ALL PRIVILEGES ON acore_world.* TO 'acore'@'localhost' WITH GRANT OPTION; GRANT ALL PRIVILEGES ON acore_characters.* TO 'acore'@'localhost' WITH GRANT OPTION; GRANT ALL PRIVILEGES ON acore_auth.* TO 'acore'@'localhost' WITH GRANT OPTION;" &&
git -C ~/ clone https://github.com/azerothcore/azerothcore-wotlk.git --branch master --single-branch azerothcore &&
git -C ~/azerothcore/modules clone https://github.com/azerothcore/mod-anticheat &&
rm -rf ~/server/data && mkdir -p ~/server/data && cd ~/server/data &&
wget https://github.com/wowgaming/client-data/releases/download/v20.0/data.zip && 7z x data.zip && rm data.zip &&
mkdir -p ~/azerothcore/build && cd ~/azerothcore/build &&
cmake ../ -DCMAKE_INSTALL_PREFIX=$HOME/server/ -DCMAKE_C_COMPILER=/usr/bin/clang -DCMAKE_CXX_COMPILER=/usr/bin/clang++ -DWITH_WARNINGS=1 -DTOOLS_BUILD=db-only -DSCRIPTS=static -DMODULES=static &&
make -j $J install &&
cp -n ~/server/etc/authserver.conf.dist ~/server/etc/authserver.conf &&
cp -n ~/server/etc/worldserver.conf.dist ~/server/etc/worldserver.conf &&
cp -n ~/server/etc/modules/Anticheat.conf.dist ~/server/etc/modules/Anticheat.conf &&
sed -i -E "s|^DataDir = .*|DataDir = \"$HOME/server/data\"|" ~/server/etc/worldserver.conf &&
sed -i -E "s|^LogsDir = .*|LogsDir = \"$HOME/server/logs\"|" ~/server/etc/*.conf &&
mkdir -p ~/server/logs &&
pm2 start $HOME/server/bin/authserver --name authserver -- -c $HOME/server/etc/authserver.conf &&
pm2 start $HOME/server/bin/worldserver --name worldserver -- -c $HOME/server/etc/worldserver.conf &&
pm2 startup && pm2 saveWhat it does, in order: packages and pm2, MySQL from the MySQL repository, the databases and the acore user, the source code, prebuilt client data, the compile, configs, start and autostart.
- Don't need the anti-cheat module? Remove the two lines with
mod-anticheatandAnticheat.conf. - If
wgetfor MySQL returns 404, the version inVis out of date. Take the current one from the mysql-apt-config download page on dev.mysql.com.
If it fails, don't start over from the top. First run rm -rf ~/azerothcore, otherwise git clone says the folder already exists. Then continue from the step that failed. In a new window, set J and V again.
Check it's running#
The server is ready when both processes in pm2 show online and the worldserver log has the line World Initialized.
pm2 status && pm2 logs worldserver --lines 30 --nostreamOn first start worldserver fills the databases itself. The log shows Applying '...sql' lines for a few minutes. To follow it live:
pm2 logs worldserverLeave with Ctrl+C; the server keeps running.
Can't set process priority class, error: Permission denied in the log is harmless. The server just couldn't raise its own priority.
Once the AC> prompt appears, you can create an account.
Account and GM rights#
Accounts are created in the worldserver console. The login is a plain username, not an email.
Open the console:
pm2 attach $(pm2 id worldserver | tr -d '[][:space:]')In the console, with your own name and password:
account create USERNAME PASSWORD
account set gmlevel USERNAME 3 -1The second command gives the account admin rights (level 3) on all realms. Ctrl+C only detaches you; the server keeps running under pm2.
Check that the account exists and has its rights:
mysql -e "SELECT a.id, a.username, aa.gmlevel, aa.RealmID FROM acore_auth.account a LEFT JOIN acore_auth.account_access aa ON aa.id = a.id;"The username is stored in capitals, gmlevel should be 3 and RealmID should be -1.
Realm address, ports, client#
Out of the box the database holds a local realm address. Players from the internet can log in but can't enter the world until you change it.
Write the address to the database. Replace x.x.x.x with the external IP or domain that clients connect to:
mysql -e "UPDATE acore_auth.realmlist SET address='x.x.x.x' WHERE id=1;"Check it:
mysql -e "SELECT id,name,address,port FROM acore_auth.realmlist;"authserver rereads the realm list about every 10 seconds. If it didn't pick up the change, restart it:
pm2 restart authserverPorts#
| Port (TCP) | Service | Purpose |
|---|---|---|
| 3724 | authserver | login |
| 8085 | worldserver | game |
Forward these two ports on your router to the server. Never open 3306 (MySQL), 7878 or 3443 to the outside.
Client#
Players need a 3.3.5a client. In realmlist.wtf (in Data/<locale>, for example Data/enUS) put one line:
set realmlist x.x.x.xFrom your home network the external address may not work if the router has no NAT loopback. In that case put the server's local IP in your own realmlist.wtf.
Maintenance#
Day-to-day control goes through pm2. Updates and backups come down to a few commands.
| Task | Command |
|---|---|
| Status | pm2 status |
| World logs | pm2 logs worldserver |
| Load per process | pm2 monit (leave with q or Ctrl+C) |
| Restart the world | pm2 restart worldserver |
| Stop everything | pm2 stop all |
| Start again | pm2 start all |
| World console | pm2 attach $(pm2 id worldserver | tr -d '[][:space:]') (leave with Ctrl+C) |
Resource usage#
Real memory and CPU per process (the RSS column is in kilobytes). Best checked 10–15 minutes after start:
ps -eo pid,comm,rss,pcpu --sort=-rss | head -n 6 && free -hUpdate#
Set J first (the command from step 1), then:
cd ~/azerothcore && git pull && git -C modules/mod-anticheat pull && cd build && cmake ../ -DCMAKE_INSTALL_PREFIX=$HOME/server/ -DCMAKE_C_COMPILER=/usr/bin/clang -DCMAKE_CXX_COMPILER=/usr/bin/clang++ -DWITH_WARNINGS=1 -DTOOLS_BUILD=db-only -DSCRIPTS=static -DMODULES=static && make -j $J install && pm2 restart authserver worldserverThe world applies new SQL updates itself on restart. The community guide also has a ready-made acoreupdate alias that does the same in one command and warns players first.
Database backup#
Uses the default user and password; change them here if you changed them.
for db in acore_auth acore_characters acore_world; do mysqldump -uacore -pacore $db > ~/$db-$(date +%F).sql; doneConfigs#
They live in ~/server/etc/: worldserver.conf and authserver.conf. Edit the line you need, then pm2 restart worldserver. The .conf.dist files stay untouched as a reference.
Change the database password#
The default is acore/acore. This command from the community guide asks for a new password and updates both MySQL and the configs:
while true; do read -s -p "Set a new SQL password: " MYSQL_PASSWORD && echo; read -s -p "Retype SQL password: " MYSQL_PASSWORD_CONFIRM && echo; [ "$MYSQL_PASSWORD" = "$MYSQL_PASSWORD_CONFIRM" ] && break || echo "Passwords did not match."; done; unset MYSQL_PASSWORD_CONFIRM && mysql -e "ALTER USER 'acore'@'localhost' IDENTIFIED BY '${MYSQL_PASSWORD}'; FLUSH PRIVILEGES;" && sed -i -E "s|= \"127.0.0.1;3306;acore;[^;]*;|= \"127.0.0.1;3306;acore;${MYSQL_PASSWORD};|" ~/server/etc/*.conf && unset MYSQL_PASSWORD && pm2 restart authserver worldserverSecurity#
For a server that's reachable from the internet, what matters most is the database password, closed ports and SSH keys.
- Database password. The default is
acore/acore. MySQL only listens on localhost, so it's tolerable to start with, but change it (see Maintenance). - Ports. Only 3724 and 8085 face the internet. Don't forward 3306 (MySQL), 7878 or 3443.
- Root. The processes run as root, which is fine for an isolated VM. Nothing blocks it, but forums advise against it: if the server is open to everyone, a separate user is safer.
- Firewall on a VPS. On a VPS facing the internet directly (not behind a home router), turn on the firewall. SSH is allowed first so you don't lock yourself out.
apt install -y ufw fail2ban && ufw default deny incoming && ufw default allow outgoing && ufw allow 22/tcp && ufw allow 3724/tcp && ufw allow 8085/tcp && ufw --force enable- SSH key instead of a password. Debian forbids root password login over SSH by default, so you need a key either way. Creating one is covered in the WoW Forever guide.
- Admin rights. Only your own account should have
gmlevel 3.
Troubleshooting#
Most install failures come down to running out of memory, leftovers from a previous attempt, or the realm address.
| Symptom | Cause | Fix |
|---|---|---|
Killed or internal compiler error in the build log | Out of memory | Lower J and finish the build: cd ~/azerothcore/build && make -j 1 install, then run the rest of the chain from the cp -n … authserver.conf.dist step |
git clone says the azerothcore folder exists | Leftover from a failed attempt | rm -rf ~/azerothcore and carry on |
wget for MySQL returns 404 | V is out of date | Take the current mysql-apt-config version from dev.mysql.com |
A package is missing on apt install | apt update warned about a repository | Read the apt update output and fix the repository |
| Login works, but you can't enter the world | Wrong address in realmlist | Update address (step 5), restart authserver |
| Realm shows as offline | Port 8085 not open, or the server isn't running | pm2 status, check forwarding of 8085 |
| Players can't log in at all | Port 3724 not open | Check forwarding of 3724 |
| World keeps restarting, the log cuts off | worldserver crashes | pm2 stop worldserver, then pm2 start worldserver --no-autorestart and read the full log |
| Can't connect from home via the external address | Router has no NAT loopback | Put the server's local IP in realmlist.wtf |
| Port 8085 conflict | Another game server on the same host | Change the world port in worldserver.conf and in realmlist |
pm2 logs are in ~/.pm2/logs, the server's own logs in ~/server/logs:
tail -n 50 ~/server/logs/Server.logIf there's no Server.log, ls ~/server/logs shows what's there.