
Every serious FiveM server needs a database. Player inventories, vehicles, properties, bank accounts, job history: all of it lives in SQL. MariaDB is the database that oxmysql recommends and Qbox requires, and setting it up correctly keeps your queries fast as player data grows.
Why MariaDB Over MySQL
FiveM frameworks reach the database through oxmysql. Its docs recommend MariaDB for compatibility: most FiveM resources were written for MySQL 5.7 and may run into problems on MySQL 8, which has more reserved keywords (such as stored and group) and, as the docs put it, no default values for LONGTEXT and JSON fields.
For FiveM specifically:
- Qbox needs MariaDB 10.9.0 or newer, recommends 12.3 LTS and does not support MySQL.
- QBCore's install guide starts by installing MariaDB.
- Ubuntu 24.04 and Debian 12 ship MariaDB 10.11 in their own repositories, so it installs with apt and meets Qbox's minimum.
Installation and Configuration
On Ubuntu or Debian:
sudo apt install mariadb-server
sudo mariadb-secure-installation
MariaDB's root account logs in through the Unix socket, so you open the SQL prompt with sudo mariadb and usually do not need a root password (MariaDB).
Create a database and user for your FiveM server:
CREATE DATABASE fivem_server;
CREATE USER 'fivem'@'localhost' IDENTIFIED BY 'strong_password_here';
GRANT ALL PRIVILEGES ON fivem_server.* TO 'fivem'@'localhost';
FLUSH PRIVILEGES;
Back up this database and the fivem account from the start: our MariaDB and MySQL backup guide dumps both every night and shows how to restore them on a new server.
MariaDB Configuration for FiveM
On Debian and Ubuntu, server settings go in /etc/mysql/mariadb.conf.d/50-server.cnf. The one worth sizing is the InnoDB buffer pool:
[mysqld]
innodb_buffer_pool_size = 1G
The buffer pool is where InnoDB keeps table data and indexes in memory, and MariaDB calls it "the most important server system variable": it should hold most of the data your server actively uses. The default is 128 MB. MariaDB allows up to 80% of RAM on a server that mainly runs InnoDB, but next to FXServer you need only what your data needs, and a pool so large that the machine starts swapping undoes the benefit. Check how big your data is:
SELECT table_schema, ROUND(SUM(data_length + index_length) / 1024 / 1024) AS size_mb
FROM information_schema.tables
GROUP BY table_schema;
Pick a size that holds your FiveM database with room to grow, for example 1G for a database of a few hundred MB, and restart MariaDB. While the server is busy, run SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read%'; twice, a minute apart: if Innodb_buffer_pool_reads grew by less than 1% of the growth in Innodb_buffer_pool_read_requests, the pool is big enough.
Leave the rest at the defaults until a measurement says otherwise:
- Do not turn on the query cache. MariaDB disables it by default because it does not scale on multi-core machines under concurrent load, and does not recommend enabling it on current hardware.
max_connectionsdefaults to 151, well above the 10 connections oxmysql's pool opens by default.tmp_table_sizeandmax_heap_table_sizedefault to 16 MB. Raise them only ifCreated_tmp_disk_tablesgrows quickly compared withCreated_tmp_tables.innodb_log_file_sizedefaults to 96 MB. Larger values mean less flushing but a slower recovery after a crash.
Connection String
In your FiveM server config, set the connection string before any resource starts, and put start oxmysql at the top of your resource list:
set mysql_connection_string "mysql://fivem:password@localhost/fivem_server?charset=utf8mb4"
oxmysql hands the parameters after the ? to the node-mysql2 connection pool. connectionLimit caps how many connections that pool opens; node-mysql2's default is 10, and waitForConnections is already true by default, so you do not need to set either. If you raise connectionLimit, keep it below MariaDB's max_connections. oxmysql's docs also warn against the characters ; , / ? : @ & = + $ # in the connection string, so avoid them in the password or switch to the user=...;password=...;host=...;database=... format.
Common Performance Issues
| Problem | Symptom | Fix |
|---|---|---|
| Slow player loading | Joining takes longer as tables grow | Run EXPLAIN on the lookup query and index the column it filters on |
| Slow script actions | oxmysql warns "RESOURCE took ...ms to execute a query" | Fix that query: add an index or fetch fewer rows |
| Database growing too large | Disk filling up | Clean old character data, archive logs; check sizes with the query above |
| Connection errors | oxmysql logs "Unable to establish a connection to the database" | Check that MariaDB is running and the connection string is right |
oxmysql prints the slow query warning for any query that takes 200 ms or more; change the limit with set mysql_slow_query_warning 150 or another value. Its docs add that the timing may not be entirely accurate, that a slow query can come from a server hitch instead of the database, and that slow queries during startup are not necessarily a problem.
Adding Indexes
An index lets MariaDB find rows without reading the whole table. Check before you add one, because the frameworks already index their main lookups: QBCore's players table uses citizenid as its primary key and has an index on license, and ESX Legacy's users table uses identifier as its primary key. A second index on those columns gains nothing.
Take the slow query from oxmysql's warning and run it with EXPLAIN:
EXPLAIN SELECT * FROM your_table WHERE owner = 'ABC12345';
If the type column says ALL, MariaDB read every row because it found no usable index, and key shows NULL. List the existing indexes, then add one on the column in the WHERE clause:
SHOW INDEX FROM your_table;
CREATE INDEX idx_owner ON your_table (owner);
Run EXPLAIN again: the key column shows which index MariaDB uses now.
Async and Await Queries
oxmysql's README describes its query handling as "non-blocking and awaitable", and its docs list two equal ways to run a query:
-- Callback: returns at once and runs the function when the rows arrive
MySQL.query('SELECT * FROM users WHERE identifier = ?', {identifier}, function(result)
-- Handle result
end)
-- Promise: waits inside this thread until the rows arrive
CreateThread(function()
local result = MySQL.query.await('SELECT * FROM users WHERE identifier = ?', {identifier})
end)
MySQL.query.await wraps the query in a promise and waits for it with Citizen.Await, which is why you call it inside a thread such as CreateThread. MySQL.Sync.fetchAll from older mysql-async scripts is an alias of this promise form. Neither form makes a slow query fast: the script that waits for the rows still waits, so fix the query itself with an index or a smaller result.
On Space-Node FiveM hosting, plans run on NVMe SSD storage, but fast storage does not fix a query that reads a whole table. Index the columns your scripts filter on, keep an eye on oxmysql's slow query warnings, and ask support which database your server can use before you deploy a framework that needs one.
Sources
All opened on 31 October 2026:
- oxmysql: docs: MySQL or MariaDB, installation, slow query warnings, docs: Query, README, config.ts, pool.ts, MySQL.lua, logger
- Cfx.re: Citizen.Wait and Lua threads
- node-mysql2: connection pools, pool_config.js
- MariaDB: InnoDB buffer pool, InnoDB system variables, server system variables, server status variables, SHOW STATUS, query cache, EXPLAIN, SHOW INDEX, CREATE INDEX, Information Schema TABLES, mariadb-secure-installation, authentication from MariaDB 10.4, Debian package config 50-server.cnf
- Packages: MariaDB in Ubuntu 24.04 and Debian 12
- Frameworks: Qbox installation, QBCore Windows installation, QBCore qbcore.sql, ESX Legacy es_extended.sql