A simple website for an AzerothCore server, written in plain PHP (no framework, no Composer, no build step). The site is available in English and Polish.
- home page: realm status (online/offline), players online, uptime and most players online
- About the server: your description and rates (experience, drop, gold, ...)
- Download: links to the game client and patches
- How to connect: a short guide with your
set realmlistline - News: posts written as text files, the latest three also on the home page
- Ranking: top characters by level and by honorable kills (with guild, faction, race and class), top arena teams for 2v2, 3v3 and 5v5; columns can be sorted by faction, race and class
- account registration and password change
- clean URLs:
/news,/ranking,/register,/changepass
| News | Arena ranking | Create account |
|---|---|---|
![]() |
![]() |
![]() |
- PHP 8.1 or newer with
pdo_mysqlandgmp(orbcmath, used automatically whengmpis missing) - Apache with
mod_rewrite, or nginx with php-fpm - access to the AzerothCore databases
acore_authandacore_characters
The recommended setup is the site on your own server: on the same machine as AzerothCore
(the simplest, MySQL stays on localhost) or on a separate machine next to it.
Example for Ubuntu/Debian with Apache, on the same machine as AzerothCore:
apt install apache2 libapache2-mod-php php-mysql php-gmp
git clone https://github.com/nolt/acore-simple-web.git /var/www/acore-simple-web
cd /var/www/acore-simple-web
# configuration
cp inc/config.sample.php inc/config.php
nano inc/config.php # site name, realmlist, database password, rates, downloads
# runtime data (status cache, rate limiting) must be writable by the web server
chown -R www-data: var
# dedicated MySQL user: set the same password as in inc/config.php first
nano sql/web-user.sql
mysql < sql/web-user.sql
# Apache virtual host pointing to public/
cp docs/apache.conf /etc/apache2/sites-available/acore-simple-web.conf
nano /etc/apache2/sites-available/acore-simple-web.conf # ServerName
a2enmod rewrite # clean URLs: /register instead of /register.php
a2dissite 000-default
a2ensite acore-simple-web
systemctl reload apache2Only public/ is served. inc/, var/, sql/ and the other directories are outside the
document root; each of them also contains an .htaccess that blocks access in case the
document root points to the project directory by mistake.
For nginx with php-fpm see docs/nginx.conf.
If your server was installed with acore-lxd-image,
run the commands above inside the container (lxc exec acore-server -- bash). MySQL is already
there, so the site connects through localhost. To reach the site from your network, forward
port 80 to the container or give it an address in your LAN (macvlan profile, see the
acore-lxd-image README).
cd /var/www/acore-simple-web
git pullYour inc/config.php, var/, your news in news/ and images in public/assets/img/custom/ are
not tracked by git and stay untouched. After an update compare inc/config.sample.php with your
config for new options.
When you update from a version without the ranking, the MySQL user needs the new read privileges:
run sql/web-user.sql again (with your address instead of localhost if the site is on another
machine). It only adds privileges; an existing user keeps its password.
- create the MySQL user for the address of the web server instead of
localhost(editsql/web-user.sql) and sethostindb_auth/db_characters - MySQL must listen on the network (
bind-addressin the MySQL configuration); allow port 3306 only from the web server in your firewall, or better connect through a VPN or an SSH tunnel acore_authandacore_charactershave separate connection settings, so they may even live on different MySQL servers- if the web server cannot reach the worldserver port, switch the status to
databasemode (see below)
Everything that depends on your installation is in inc/config.php:
| Section | Meaning |
|---|---|
site |
server name, languages, optional background image |
realm |
realmlist address and client version shown in "How to connect" |
about |
description and rates shown in "About the server" |
downloads |
links shown in "Download" |
db_auth, db_characters |
database connections |
status |
how the realm status is detected, cache time |
ranking |
entries per list (default 50), hiding GM characters, cache time |
news |
news per page, how many of them the home page shows |
account |
email required or optional, expansion of new accounts, minimal password length |
rate_limit |
allowed attempts per IP address (and per account for password changes) |
Texts in about and downloads are given per language; a plain string is used for every language:
'about' => [
'text' => [
'en' => 'A private realm powered by AzerothCore.',
'pl' => 'Prywatny serwer oparty na AzerothCore.',
],
'rates' => [
['label' => ['en' => 'Experience', 'pl' => 'Doświadczenie'], 'value' => 'x5'],
['label' => ['en' => 'Item drop', 'pl' => 'Drop przedmiotów'], 'value' => 'x3'],
],
],
'downloads' => [
['label' => ['en' => 'Game client 3.3.5a', 'pl' => 'Klient gry 3.3.5a'],
'url' => 'https://example.com/wow-3.3.5a.zip', 'note' => '17 GB'],
],Rates are not read from the server; copy them from your worldserver.conf
(Rate.XP.Kill, Rate.Drop.Item.*, Rate.Drop.Money, ...). A section without content is hidden.
tcp(default): the realm is online when the worldserver port (8085) accepts a connectiondatabase: the realm is online when the worldserver updatedacore_auth.uptimein the lastmax_ageseconds. The worldserver writes it everyUpdateUptimeIntervalminutes (worldserver.conf, default 10), so a stopped server is detected with that delay.
Players online are counted from acore_characters.characters, uptime and the record come from
acore_auth.uptime. The status is cached for cache_ttl seconds.
News are text files in the news/ directory of the site (next to inc/ and public/, not served
directly and ignored by git). A new file shows up right away, deleting it removes the news.
The file name starts with the date in the YYYY-MM-DD format (year-month-day), which also sets the order, newest first:
news/2026-09-26-server-open.md one text for every language
news/2026-09-26-server-open.en.md English version
news/2026-09-26-server-open.pl.md Polish version of the same news
When the current language has no file, the one without a language is used, then any other.
A file with an impossible date (e.g. 2026-26-09, day and month swapped) is skipped and written
to the web server error log. The date is shown in the format of the language (Sep 26, 2026,
26.09.2026).
The first line # ... is the title, the rest is the text:
# The server is live!
After weeks of testing we are opening the gates.
To start with:
- **x5** experience and **x3** drop,
- a link to the [AzerothCore docs](https://www.azerothcore.org).
An empty line starts a new paragraph, lines starting with - make a list, **text** is bold and
[text](https://...) is a link. Any other HTML is shown as plain text.
- Level: all characters by level, then experience
- PvP: characters with at least one honorable kill, by the number of kills
- Arena 2v2 / 3v3 / 5v5: teams by rating, with members, the faction of the captain and the wins and games of the season
Each list shows up to ranking.limit entries (50), or fewer when there are not so many players.
Characters of accounts with a GM level (acore_auth.account_access) and deleted characters are
left out; set hide_gm to false to show GMs too. Clicking the Faction, Race or Class header
sorts the list alphabetically, the # column keeps the ranking position. The lists are cached
for cache_ttl seconds (300).
- background: the default is
public/assets/img/background.webp(AI generated, 2560 px wide). To use your own image, put it intopublic/assets/img/custom/(ignored by git) and set its path insite.background, e.g.'assets/img/custom/background.webp'. - accent color: four variables at the top of
public/assets/css/style.css(--primary,--primary-rgb,--primary-dark,--on-primary) color the whole site. - translations:
inc/lang/en.phpandinc/lang/pl.php; add a language by copying one of them and adding its code tosite.languages.
The site does exactly what AzerothCore does in AccountMgr::CreateAccount and ChangePassword:
- the account name and email are stored in upper case, like the server does
- the password is never stored: the site computes the SRP6
saltandverifierused by the 3.3.5a login (from the upper-cased name and password) - a character counter is added to
realmcharactersfor every realm - limits follow AzerothCore: account name up to 17 characters, password up to 16
The site accepts only letters and digits in account names and printable ASCII without spaces in passwords, so everything can be typed on the game login screen.
- dedicated MySQL user with column level privileges (
sql/web-user.sql): the site can create accounts and replacesalt/verifier, but cannot change anything else, e.g. GM levels. The ranking only reads the columns it shows (level, kills, guild and arena team names, GM level) - CSRF tokens on all forms, a honeypot field against bots, rate limiting per IP address (and per account for password changes)
- wrong account name and wrong password give the same answer in the same time, so the password form does not reveal which accounts exist
- prepared statements only, security headers (Content-Security-Policy, X-Frame-Options, ...)
- news are escaped, only the formatting described above becomes HTML; links must start with
http://orhttps://
The SRP6 implementation is checked against verifiers computed independently:
php tests/srp6-test.php



