Chapter 4
Chapter 4 — MySQL Configuration
A freshly installed MySQL runs on conservative defaults designed to work on any hardware. On your actual server, with a known amount of RAM and a specific workload, those defaults leave significant performance on the table — and also omit important diagnostics like the slow query log. This chapter covers finding your config files, the key settings that matter for a shared web server, and how to apply changes safely.
SET GLOBAL, SET PERSIST) and when to use each. Validating config before restart. The single most important tuning setting: innodb_buffer_pool_size. Connection limits and timeout settings. The slow query log — the most valuable diagnostic tool in MySQL. Default character set. A complete annotated config file for a shared web+database server. Troubleshooting.
The Configuration File Hierarchy
MySQL reads multiple config files at startup. Options in later files override earlier ones. Knowing the order matters — editing the wrong file has no effect.
/etc/mysql/mysql.conf.d/mysqld.cnf. This is the primary MySQL server configuration file. The top-level /etc/mysql/my.cnf just contains !includedir lines that pull in the subdirectories — editing it directly achieves nothing for server settings. On Debian with the MySQL APT repo, the same file structure applies.
You can also add your own override files in a conf.d or mysql.conf.d drop-in directory rather than editing the main file — useful for keeping your customisations separate and easier to manage in version control.
Three Ways to Change a Setting
SET GLOBAL max_connections = 100;. Good for testing a value. Takes effect instantly for new connections./var/lib/mysql/mysqld-auto.cnf, so it survives a restart. Best of both worlds for dynamic variables.mysqld-auto.cnf: RESET PERSIST max_connections;. Allows the config file value (or default) to take over again.Validating config before restart
systemctl restart mysql closes all active database connections. If your website has a connection pool, connections will fail and reconnect after MySQL comes back up, typically within a few seconds. Static variables require a restart; dynamic ones changed via SET GLOBAL or SET PERSIST take effect immediately without disconnect.
The Most Important Setting: innodb_buffer_pool_size
InnoDB (MySQL's default storage engine) caches frequently-accessed table data and index pages in a memory pool called the buffer pool. If a query's data is in the buffer pool, it's served from RAM — microseconds. If it's not, MySQL reads from disk — milliseconds or worse. The buffer pool size is the single biggest lever for MySQL performance.
The default is 128 MB — enough to run on a tiny VM, far too small for any real workload. For a shared web server running Apache, PHP, and MySQL, allocate 20–30% of total system RAM to the buffer pool.
SET GLOBAL innodb_buffer_pool_size, but this is a slow operation that resizes chunk by chunk in the background.
Connection Settings
Each MySQL connection consumes RAM — stack space, buffers, and per-connection memory allocations. Setting max_connections too high wastes memory; too low and legitimate connections are refused with "Too many connections."
| Variable | Default | Recommended (shared server) | Notes |
|---|---|---|---|
| max_connections | 151 | 50–100 | Each idle connection uses ~1 MB. Match to Max_used_connections + 20% headroom |
| wait_timeout | 28800 | 300 | Seconds before an idle non-interactive connection is closed. 28800 = 8 hours — far too long for web apps |
| interactive_timeout | 28800 | 300 | Same but for interactive clients (mysql CLI, phpMyAdmin). Set to same value as wait_timeout |
| max_allowed_packet | 64M (8.0) | 64M–256M | Max size of a single query/result packet. Increase if you store large blobs or get "packet too large" errors |
| connect_timeout | 10 | leave default | Seconds to wait for a client to authenticate. 10 seconds is fine |
sort_buffer_size, join_buffer_size, and read_buffer_size are allocated per connection when needed. If you have 100 connections each using a 4 MB sort buffer simultaneously, that's 400 MB — just for sort operations. Leave these at their defaults unless you have a specific query pattern that needs them.
The Slow Query Log — Your Most Valuable Diagnostic Tool
MySQL can log every query that takes longer than a threshold you define. This log is the starting point for almost every performance investigation — it shows you exactly which queries are slow, how long they took, and how many rows they examined. Enable it from day one; the overhead on a small server is negligible.
Enable it permanently by adding these lines to mysqld.cnf, then restart:
Reading the slow log
Each entry in the slow log shows the query's cost: how long it took, how many rows MySQL looked at, and how many it returned.
Summarising the slow log with mysqldumpslow
Default Character Set and Collation
By default, MySQL 8.0 uses utf8mb4 and utf8mb4_0900_ai_ci — but earlier versions and some custom installs use the 3-byte utf8 which can't store emoji or certain CJK characters. Set these explicitly so every new database inherits the right encoding automatically.
utf8mb4_0900_ai_ci is the MySQL 8.0 default — faster and uses the Unicode 9.0 algorithm. utf8mb4_unicode_ci is the MySQL 5.x default and may be more appropriate if you need consistent sorting across old and new MySQL versions, or if you're following documentation written for MySQL 5.x. Either is fine for a single-server setup.
A Tuned Config for a Shared Web + Database Server
Here is a complete, annotated mysqld.cnf for a server that runs Apache, PHP, and MySQL together. Adjust the memory values to match your server's RAM using the guide above.
Binary Logging — Brief Overview
The binary log (binlog) records every data change on the server. It's required for replication and enables point-in-time recovery (PITR) — restoring a backup from Monday, then replaying binlog events up to the moment before a disaster on Wednesday afternoon.
disable_log_bin to mysqld.cnf — but think carefully before doing so. PITR is what lets you recover from "someone accidentally ran DELETE without WHERE" — a situation that happens sooner or later to everyone.
Verifying Your Config Changes
Troubleshooting
sudo mysqld --validate-config first to see the exact error line — it will name the unknown or invalid variable. If MySQL is already not starting, check the error log: sudo tail -30 /var/log/mysql/error.log. Common causes: typo in variable name, wrong value format (wrote 1G where the variable expects bytes), editing the wrong config file (changes in /etc/mysql/my.cnf itself have no effect — only files included from it do). To recover, revert your last change and restart.
SET PERSIST override in /var/lib/mysql/mysqld-auto.cnf that takes precedence over mysqld.cnf. Check: SELECT variable_name, variable_value FROM performance_schema.persisted_variables;. If the old value appears there, clear it: RESET PERSIST innodb_buffer_pool_size; then restart. After resetting, the config file value will be used.
mysql OS user must own the log directory. Check: ls -la /var/log/mysql/ — should be mysql:adm or mysql:mysql. Fix: sudo chown mysql:mysql /var/log/mysql/slow.log or sudo touch /var/log/mysql/slow.log && sudo chown mysql:mysql /var/log/mysql/slow.log. Then SET GLOBAL slow_query_log = OFF; SET GLOBAL slow_query_log = ON; to make MySQL re-open the file.
max_connections has been reached. Immediate fix: SET GLOBAL max_connections = 200; (dynamic, takes effect immediately). Root cause: check SHOW PROCESSLIST; for Sleep connections piling up — if many show Sleep for a long time, reduce wait_timeout to close them sooner (e.g., SET PERSIST wait_timeout = 60;). The underlying problem is often a connection pool in your application not returning connections properly.
innodb_buffer_pool_size too large — MySQL is competing with Apache and the OS for RAM, causing swapping. A swapping server is slower than one with a small buffer pool. Roll back: set the value to 25% of RAM instead of a higher percentage. Remember that each max_connections connection also allocates buffers — total MySQL memory ≈ innodb_buffer_pool_size + (max_connections × per-connection buffers). On a 2 GB server, keep the buffer pool at 256–512 MB.
Quick Reference — Chapter 4
| Variable / Command | Purpose / Notes |
|---|---|
| /etc/mysql/mysql.conf.d/mysqld.cnf | Primary server config file on Ubuntu/Debian — this is where you make changes |
| sudo mysqld --validate-config | Check the config file for errors before restarting |
| SHOW VARIABLES LIKE 'pattern'; | See current value of any MySQL variable (supports % wildcard) |
| SET PERSIST variable = value; | Change a dynamic variable immediately AND persist it across restarts (MySQL 8.0+) |
| RESET PERSIST variable; | Remove a persisted override so the config file value takes effect again |
| innodb_buffer_pool_size | Most important setting — set to 20–30% of RAM on shared servers. Requires restart. |
| max_connections | Cap total connections. Match to Max_used_connections status + 20% headroom |
| wait_timeout / interactive_timeout | Seconds before idle connections are closed. Set both to 300 on web servers |
| slow_query_log = 1 | Enable the slow query log (dynamic — no restart needed) |
| long_query_time = 2 | Log queries taking longer than 2 seconds |
| log_queries_not_using_indexes = 1 | Also log full table scans — essential for finding missing indexes |
| sudo mysqldumpslow -s t -t 10 /var/log/mysql/slow.log | Summarise slow log — top 10 worst queries by total execution time |
| SHOW STATUS LIKE 'Innodb_buffer_pool_read%'; | Buffer pool hit ratio — if below 95%, pool is too small |
| SHOW STATUS LIKE 'Max_used_connections'; | Peak concurrent connections since last restart — use to size max_connections |