A slow ERPNext site with few users is usually a server setup problem, not a traffic problem. On one self-hosted Frappe server we checked, MariaDB was still on its default 128 MB buffer pool while serving a 2.5 GB database.
That server had four problems that are easy to miss:
- MariaDB was running with default memory settings.
- Backups for every site started at once, during working hours.
- Demo sites were still using server resources.
- A memory script kept restarting the web workers.
This guide shows how to check each one on your own Ubuntu server. It assumes your bench is in ~/frappe-bench, so change the path if yours is different.
Step 1: Check the server’s basics
Start with four quick commands. They show how many sites the server carries and how much room it has left.
ls ~/frappe-bench/sites/*/site_config.json | wc -l
nproc
free -h
df -h
Pay particular attention to swap in the free -h output. Swap in use while plenty of memory is available means the server probably ran short of RAM earlier. Next, see what is using that memory, and what ended up in swap:
ps -eo rss=,comm= | awk '{m[$2]+=$1} END{for(p in m) printf "%.0f MB %s\n", m[p]/1024, p}' | sort -rn | head -10
for f in /proc/[0-9]*/status; do awk '/^Name:/{n=$2} /^VmSwap:/{if($2>0) printf "%.0f MB %s\n", $2/1024, n}' "$f" 2>/dev/null; done | sort -rn | head -10
Redis or MariaDB in swap is a warning sign, because Frappe relies on both for normal requests. Gunicorn’s total in the first list can look too high, since its workers share memory that is counted once per process.
Step 2: Check whether MariaDB has enough memory
A fresh MariaDB install sets innodb_buffer_pool_size to 128 MB, and that is often too small for ERPNext. This setting controls how much data stays in RAM, so a small pool means more slow disk reads.
Check your current settings:
sudo mysql -e "SHOW GLOBAL VARIABLES WHERE Variable_name IN ('innodb_buffer_pool_size','table_open_cache','table_definition_cache','max_connections');"
Then check your database size and table count. Run this at a quiet time, because it can be slow:
sudo mysql -e "SELECT COUNT(*) AS total_tables, ROUND(SUM(data_length+index_length)/1024/1024/1024,1) AS size_gb FROM information_schema.tables WHERE table_schema NOT IN ('mysql','information_schema','performance_schema','sys');"
Finally, see how often MariaDB has to go to disk:
sudo mysql -e "SHOW GLOBAL STATUS WHERE Variable_name IN ('Uptime','Innodb_buffer_pool_read_requests','Innodb_buffer_pool_reads','Opened_table_definitions');"
Divide Innodb_buffer_pool_reads by Innodb_buffer_pool_read_requests. Above about 1%, the pool is likely too small. A table_definition_cache far below your table count is also a problem, and Opened_table_definitions will keep climbing.
Here is what the server in our example showed:
| Check | Result | Meaning |
|---|---|---|
| Buffer pool | 128 MB | About 5% of the database fits in the pool |
| Database size | 2.5 GB | Far larger than the pool |
| Disk reads | 5.4% of read requests | Well above the 1% guide |
| Table definition cache | 400, for 9,072 tables | Definitions were reloaded nearly twice a second |
Step 3: Give MariaDB more memory
Set the buffer pool a little larger than your database, as long as the server keeps enough free RAM for everything else. Our 2.5 GB database got a 3 GB pool on a 15 GB server, with about 7 GB available before the change.
Before you start: read Step 6 if your server has a script that restarts services based on memory use. A bigger pool can trigger that script repeatedly.
Create a settings file. Adjust these values to your own database and table count:
printf '[mysqld]\ninnodb_buffer_pool_size = 3G\ntable_definition_cache = 10000\ntable_open_cache = 6000\n' | sudo tee /etc/mysql/mariadb.conf.d/99-frappe-tuning.cnf
Restart MariaDB outside working hours, because every site is briefly interrupted:
sudo systemctl restart mariadb
Confirm that the new values are active:
sudo mysql -e "SELECT @@innodb_buffer_pool_size, @@table_definition_cache, @@table_open_cache, @@open_files_limit;"
A 3 GB pool shows as 3221225472. Sites may feel slower for a few minutes while the cache refills, and available memory will drop as the pool fills. If MariaDB does not start, delete the file and restart it again.
Step 4: Move backups out of working hours
Slowdowns that return every few hours often line up with backups. A common bench setup backs up all sites every 6 hours, so check your schedule and your server’s time zone:
crontab -l
timedatectl | grep "Time zone"
Cron uses server time, and that is often UTC. A job at 0 */6 * * * on a UTC server runs at 5:30 AM, 11:30 AM, 5:30 PM and 11:30 PM in India, so two runs land in the working day.
Open your crontab with crontab -e and move the backup to a quiet time. This line runs at 2:00 AM IST on a UTC server:
30 20 * * * cd /home/ubuntu/frappe-bench && /home/ubuntu/.local/bin/bench --verbose --site all backup >> /home/ubuntu/frappe-bench/logs/backup.log 2>&1
Keep your own paths from the existing line. Running backups once a day also means you could lose up to 24 hours of data, so give busy client sites their own, more frequent backup. Delete any cron lines that point to folders no longer on the server.
Step 5: Clean up demo and unused sites
A site with no visitors can still cost you resources. Each one adds database tables, backup work and, if its scheduler is on, background jobs. Our example server went from 25 sites to 10 by removing sites it no longer needed.
For a site you still want but rarely use, turn off its scheduler:
cd ~/frappe-bench && bench --site demo.example.com scheduler disable
The site’s scheduled emails and background jobs will stop until you turn the scheduler back on, so only do this for sites that do not depend on them.
For a site you no longer need, back it up and check the backup before you drop it:
cd ~/frappe-bench && bench --site demo.example.com backup --with-files
cd ~/frappe-bench && bench drop-site demo.example.com
Dropping a site deletes its database, so make sure you have the right site name first. Afterwards, compare sudo mysql -e "SHOW DATABASES;" with your remaining sites to confirm nothing was left behind.
Step 6: Check any memory restart scripts
A script that restarts web workers on high memory use can itself make clients see slow pages or failed requests. Our example server ran one every 5 minutes, and it restarted all 9 workers whenever used memory reached 10 GB. Its log showed 34 restarts.
The trigger was the real problem. Used memory is a poor signal, because a healthy database is meant to hold a lot of RAM. With a larger buffer pool, that server could have stayed above 10 GB and restarted its workers every 5 minutes.
If you use a script like this, base it on available memory instead. It should act only when the server is truly short of memory:
MIN_AVAILABLE_MB=1500
AVAILABLE_MB=$(free -m | awk '/^Mem:/{print $7}')
if [ "$AVAILABLE_MB" -lt "$MIN_AVAILABLE_MB" ]; then
sudo /usr/bin/supervisorctl restart frappe-bench-web:
fi
Change frappe-bench-web: to match your bench’s name. Treat this as a safety net, and look into why memory is growing if it keeps firing.
Step 7: Confirm the fix
These changes work only if the numbers improve under real use, so check again after a full working day. Turn on tracking before that day starts:
sudo apt install -y sysstat
sudo systemctl enable --now sysstat
sudo mysql -e "SET GLOBAL slow_query_log=1; SET GLOBAL long_query_time=2;"
The slow query setting records queries over 2 seconds, and it resets when MariaDB restarts. After the working day, look for these results:
- Disk reads are under 1% of read requests.
Opened_table_definitionshas nearly stopped rising.- Your restart script has not fired again.
sar -ushows no long periods of high%iowaitor%steal.- Clients report fewer slow periods.
If sites are still slow, note the exact times. Compare them with your slow query log and sar history to find the query or job responsible.
Quick checklist
- Review any memory restart script
- Set the MariaDB buffer pool to fit your database
- Raise
table_definition_cacheandtable_open_cache - Move backups out of working hours
- Disable the scheduler on demo sites
- Drop sites you no longer need
- Turn on tracking and check the results after a working day
Test these changes outside working hours, and make sure you have a recent backup first.