MySQLServer Configuration (my.cnf)

MySQL Configuration

MySQL's behavior is controlled by its configuration file and runtime system variables. Understanding the key settings and how to tune them for your workload is essential for running a healthy production database. The wrong defaults — especially for the InnoDB buffer pool — can make a well-designed schema perform poorly.

Configuration File Locations

MySQL reads configuration from my.cnf (Linux/macOS) or my.ini (Windows). Multiple locations are checked in order — settings in later files override earlier ones:

Platform

File Search Order

Linux (system-wide)

/etc/my.cnf, /etc/mysql/my.cnf, /etc/mysql/conf.d/*.cnf

Linux (user-specific)

~/.my.cnf

macOS Homebrew

/usr/local/etc/my.cnf, /opt/homebrew/etc/my.cnf, ~/.my.cnf

Windows

C:\ProgramData\MySQL\MySQL Server 8.0\my.ini, C:\Windows\my.ini

Bash
# Find which config files MySQL is actually reading on your system
mysql --help | grep -A1 'Default options'
# Output: /etc/my.cnf /etc/mysql/my.cnf ~/.my.cnf

# Show which option files were used for a running instance
SELECT @@global.datadir;    -- data directory also shows install path
Configuration File Structure

Bash
# /etc/mysql/my.cnf — annotated production example

[client]
port     = 3306
socket   = /var/run/mysqld/mysqld.sock

[mysqld]
# --- Basic ---
user             = mysql
port             = 3306
datadir          = /var/lib/mysql
socket           = /var/run/mysqld/mysqld.sock
pid-file         = /var/run/mysqld/mysqld.pid

# --- Character Set (always set explicitly) ---
character_set_server = utf8mb4
collation_server     = utf8mb4_unicode_ci

# --- InnoDB (most important section) ---
innodb_buffer_pool_size        = 12G   # 75% of 16 GB server
innodb_buffer_pool_instances   = 8
innodb_log_file_size           = 512M
innodb_flush_log_at_trx_commit = 1     # ACID safe
innodb_io_capacity             = 2000  # for SSD

# --- Connections ---
max_connections      = 200
wait_timeout         = 28800
interactive_timeout  = 28800
thread_cache_size    = 16

# --- Binary Log ---
log_bin              = /var/log/mysql/mysql-bin.log
binlog_format        = ROW
expire_logs_days     = 7
sync_binlog          = 1

# --- Slow Query Log ---
slow_query_log       = ON
slow_query_log_file  = /var/log/mysql/mysql-slow.log
long_query_time      = 1
Note
After editing my.cnf, MySQL must be restarted for static variables to take effect. Use SET PERSIST (MySQL 8.0+) to change dynamic variables at runtime and have them survive restarts without manual file editing.
Critical InnoDB Settings

Variable

Default

Recommended

Description

innodb_buffer_pool_size

128M

70–80% of RAM on dedicated server

Most impactful setting — caches data and index pages in memory. If your working dataset fits here, most queries avoid disk I/O.

innodb_buffer_pool_instances

1

1 per GB up to 64

Splits the buffer pool into multiple independent segments to reduce mutex contention on multi-core servers.

innodb_log_file_size

48M

256M–2G

Larger redo log files mean fewer checkpoint flushes and faster bulk INSERT/UPDATE. Requires clean restart when changed.

innodb_flush_log_at_trx_commit

1

1 (ACID) or 2 (faster with risk)

1 = flush redo log on every commit (ACID, safest). 2 = flush every second (up to 1s data loss on crash). 0 = flush every second and skip fsync (dangerous).

innodb_io_capacity

200

2000+ for SSD

Controls I/O operations InnoDB can perform per second. The 200 default is tuned for spinning disks — raise to 2000–4000 for NVMe/SSD.

innodb_file_per_table

ON

ON

Each table gets its own .ibd file. Enables TRUNCATE to reclaim space and simplifies table-level operations.

Bash
[mysqld]
# InnoDB tuning for a 32 GB dedicated database server with SSD
innodb_buffer_pool_size        = 24G   # 75% of 32 GB
innodb_buffer_pool_instances   = 8     # one per ~3 GB
innodb_log_file_size           = 1G
innodb_flush_log_at_trx_commit = 1     # ACID compliant
innodb_io_capacity             = 4000  # NVMe SSD
innodb_io_capacity_max         = 8000
Warning
After changing innodb_log_file_size, stop MySQL cleanly and then remove the old ib_logfile0 and ib_logfile1 files before restarting. MySQL will recreate them at the new size. Never change this on a running server without a clean shutdown.
Connection Settings

Bash
[mysqld]
# Maximum simultaneous connections (each uses ~1 MB RAM)
max_connections       = 200

# Seconds an idle non-interactive connection waits before MySQL closes it
wait_timeout          = 28800   # 8 hours (reduce to 300 in production with pooling)

# Seconds for interactive sessions (mysql CLI)
interactive_timeout   = 28800

# Connection backlog queue
back_log              = 150

# Thread cache — reuse threads instead of creating/destroying per connection
thread_cache_size     = 32

Sizing max_connections: each connection uses approximately 1 MB of RAM for stack and buffers. Setting it to 1000 uses ~1 GB just for connections. Use a connection pool (ProxySQL, application pool) to keep max_connections under 500.

Binary Log Settings

Bash
[mysqld]
# Enable binary logging (required for replication and point-in-time recovery)
log_bin                    = /var/log/mysql/mysql-bin.log
binlog_format              = ROW              # safest for replication
binlog_row_image           = MINIMAL          # log only changed columns
sync_binlog                = 1                # flush to disk on each commit

# Retention (MySQL 5.7 uses expire_logs_days; 8.0 uses binlog_expire_logs_seconds)
expire_logs_days           = 7                # MySQL 5.7
# binlog_expire_logs_seconds = 604800         # MySQL 8.0 (7 days)

max_binlog_size            = 100M             # rotate file when it reaches this size
Tip
Set binlog_format = ROW in production. Statement-based logging (STATEMENT) can produce inconsistent replicas with non-deterministic functions like NOW(), UUID(), and RAND().
Viewing Variables at Runtime

SQL
-- Show all variables (very long list)
SHOW VARIABLES;

-- Filter by pattern
SHOW VARIABLES LIKE 'innodb_buffer%';
SHOW VARIABLES LIKE 'max_connections';

-- From performance_schema (more detail, filterable)
SELECT VARIABLE_NAME, VARIABLE_VALUE
FROM performance_schema.global_variables
WHERE VARIABLE_NAME LIKE 'innodb%'
ORDER BY VARIABLE_NAME;

-- Check current session variables
SHOW SESSION VARIABLES LIKE 'long_query_time';
+----------------------------+-------+
| Variable_name              | Value |
+----------------------------+-------+
| innodb_buffer_pool_size    | 4294967296 |
| innodb_buffer_pool_instances | 8   |
| innodb_log_file_size       | 536870912  |
+----------------------------+-------+
Changing Variables at Runtime

SQL
-- Change globally (applies to all new connections immediately)
SET GLOBAL max_connections = 300;
SET GLOBAL slow_query_log  = ON;

-- Change for current session only (does not affect other connections)
SET SESSION long_query_time = 0.5;
SET SESSION sql_mode = 'STRICT_TRANS_TABLES';

-- MySQL 8.0+: persist to mysqld-auto.cnf (survives restart, no file editing needed)
SET PERSIST max_connections = 300;

-- MySQL 8.0+: persist only — does NOT apply to the running instance
-- (use for variables that require restart, like innodb_log_file_size)
SET PERSIST_ONLY innodb_log_file_size = 1073741824;  -- 1G, takes effect after restart
Note
SET PERSIST writes to mysqld-auto.cnf in the data directory. On next startup, MySQL applies both my.cnf and mysqld-auto.cnf, with the latter taking precedence.
Dynamic vs Static Variables

Type

Can Change at Runtime?

Examples

Dynamic

Yes — SET GLOBAL or SET SESSION

max_connections, long_query_time, slow_query_log, innodb_buffer_pool_size (8.0+)

Static

No — requires restart

datadir, socket, port, innodb_log_file_size (pre-8.0)

Partially dynamic (8.0+)

Some previously static variables are now dynamic

innodb_buffer_pool_size can be resized online in 8.0

SET PERSIST and Persistent Variables

SQL
-- View what has been persisted via SET PERSIST or SET PERSIST_ONLY
SELECT * FROM performance_schema.persisted_variables;

-- Remove a persisted variable (reverts to my.cnf value on next restart)
RESET PERSIST max_connections;

-- Reset ALL persisted variables
RESET PERSIST;
mysqld_safe vs systemd

Modern Linux distributions use systemd to manage MySQL:

Bash
# systemd (modern Linux — preferred)
systemctl start mysql
systemctl stop  mysql
systemctl restart mysql
systemctl status mysql
systemctl enable mysql    # start on boot

# mysqld_safe (legacy wrapper script — still used on some systems)
mysqld_safe --user=mysql &
mysqladmin -u root -p shutdown
Performance Schema Overhead

Performance Schema (P_S) is enabled by default since MySQL 5.7. It adds observability but has a small overhead. In most workloads the overhead is 2–5% and is worth it for the diagnostic value. If you are running a very latency-sensitive workload:

Bash
[mysqld]
# Disable Performance Schema entirely (not recommended in production)
performance_schema = OFF

# Or reduce P_S overhead by disabling specific expensive consumers:
# Disable in mysql client at runtime:
# UPDATE performance_schema.setup_consumers
# SET ENABLED = 'NO' WHERE NAME = 'events_statements_history_long';
Key Configuration Quick Reference

Variable

Recommended Value

innodb_buffer_pool_size

70–80% of total RAM on a dedicated server

innodb_buffer_pool_instances

1 per GB of buffer pool, up to 64

innodb_log_file_size

256M–2G depending on write volume

innodb_flush_log_at_trx_commit

1 for ACID; 2 for performance with acceptable risk

max_connections

No more than 3x the max concurrent app threads; use connection pooling

slow_query_log

ON — always enable in production

long_query_time

1 second is a good starting point; 0.1 for fine-grained tuning

sync_binlog

1 for ACID compliance; 0 or 1000 for maximum write speed with data loss risk

character_set_server

utf8mb4

collation_server

utf8mb4_unicode_ci or utf8mb4_0900_ai_ci (MySQL 8.0)

binlog_format

ROW (safest for replication consistency)

General Log (Development Debugging)

The general log captures every statement sent to MySQL — useful for debugging application code but far too verbose for production:

SQL
-- Enable general log at runtime (do NOT leave this on in production)
SET GLOBAL general_log = ON;
SET GLOBAL general_log_file = '/var/log/mysql/mysql-general.log';

-- Watch what your application sends to MySQL in real time
SHOW VARIABLES LIKE 'general_log%';

-- Disable after debugging
SET GLOBAL general_log = OFF;
Warning
The general log writes every single query to disk — including SELECTs, INSERTs, and connection events. On a busy production server this will quickly fill your disk and severely impact performance. Enable it only for brief debugging sessions.
Error Log Configuration

Bash
[mysqld]
# Error log — always enable in production
log_error          = /var/log/mysql/mysql-error.log
log_error_verbosity = 2   # 1=errors only, 2=errors+warnings, 3=notes

# On systemd systems, log to journald instead
# log_error = stderr
Tuning for Specific Workloads

Workload

Key Variables to Tune

Direction

Read-heavy (reporting)

innodb_buffer_pool_size, read_buffer_size

Maximize buffer pool; add read replicas

Write-heavy (events, logging)

innodb_log_file_size, innodb_flush_log_at_trx_commit, sync_binlog

Larger log files; consider flush=2 for non-critical data

Mixed OLTP

innodb_buffer_pool_size, max_connections, thread_cache_size

Balanced; use connection pooling; keep transactions short

Bulk load / ETL

innodb_flush_log_at_trx_commit, foreign_key_checks, unique_checks

Set to 0/OFF during load; re-enable after

Many small connections

thread_cache_size, back_log, max_connections

Increase thread cache; use connection pooler

Temporary Table Settings

EXPLAIN showing "Using temporary" means MySQL created an internal temporary table. If this happens frequently for large result sets, increase the in-memory temp table limits:

Bash
[mysqld]
# Internal temporary table size limits
tmp_table_size        = 64M    # max size of an in-memory temp table
max_heap_table_size   = 64M    # max size of user-created MEMORY tables
# If a temp table exceeds tmp_table_size, it spills to disk (much slower)
# Monitor with: SHOW GLOBAL STATUS LIKE 'Created_tmp_disk_tables';
Verify Configuration is Applied

SQL
-- Verify a variable is set to what you expect
SELECT @@global.innodb_buffer_pool_size / 1024 / 1024 / 1024 AS buffer_pool_gb;
SELECT @@global.max_connections;
SELECT @@global.slow_query_log;

-- Check if SET PERSIST has been applied
SELECT * FROM performance_schema.persisted_variables
ORDER BY VARIABLE_NAME;

-- Check running instance vs persisted values
SELECT g.VARIABLE_NAME,
       g.VARIABLE_VALUE  AS runtime_value,
       p.VARIABLE_VALUE  AS persisted_value
FROM performance_schema.global_variables g
LEFT JOIN performance_schema.persisted_variables p USING (VARIABLE_NAME)
WHERE p.VARIABLE_VALUE IS NOT NULL
ORDER BY g.VARIABLE_NAME;
SQL Mode Settings

SQL mode controls how MySQL handles invalid data and certain SQL syntax. MySQL 8.0 defaults are stricter than 5.7:

SQL
-- Check current SQL mode
SELECT @@sql_mode;

-- MySQL 8.0 default includes STRICT_TRANS_TABLES, which rejects
-- invalid data instead of silently truncating or substituting defaults

-- Common modes and what they control:
-- STRICT_TRANS_TABLES: reject invalid inserts (wrong type, too long, out of range)
-- NO_ZERO_DATE: reject '0000-00-00' as a valid date
-- NO_ZERO_IN_DATE: reject dates like '2024-00-15'
-- ONLY_FULL_GROUP_BY: require GROUP BY to include all non-aggregate SELECT columns
-- ERROR_FOR_DIVISION_BY_ZERO: return error instead of NULL for divide by zero

-- Set a custom SQL mode (production recommendation)
SET GLOBAL sql_mode = 'STRICT_TRANS_TABLES,NO_ZERO_DATE,NO_ZERO_IN_DATE,ERROR_FOR_DIVISION_BY_ZERO,ONLY_FULL_GROUP_BY';

-- In my.cnf:
-- [mysqld]
-- sql_mode = STRICT_TRANS_TABLES,NO_ZERO_DATE,NO_ZERO_IN_DATE,ERROR_FOR_DIVISION_BY_ZERO,ONLY_FULL_GROUP_BY
Minimal Starter Configuration for a 4 GB VPS

Bash
[mysqld]
# Character set
character_set_server = utf8mb4
collation_server     = utf8mb4_unicode_ci

# InnoDB — set to 50-60% of RAM on a shared VPS (OS needs memory too)
innodb_buffer_pool_size        = 2G
innodb_log_file_size           = 256M
innodb_flush_log_at_trx_commit = 1

# Connections
max_connections     = 100
wait_timeout        = 300        # 5 minutes idle timeout
thread_cache_size   = 8

# Logging
slow_query_log      = ON
slow_query_log_file = /var/log/mysql/mysql-slow.log
long_query_time     = 1
log_error           = /var/log/mysql/mysql-error.log
Configuration Best Practices
  1. Start with a validated baseline (mysqltuner.pl provides evidence-based recommendations from your actual status).

  2. Change one variable at a time and benchmark before changing the next.

  3. Use SET PERSIST (MySQL 8.0) to avoid manual my.cnf edits and the risk of config drift between file and runtime.

  4. Monitor SHOW STATUS and SHOW ENGINE INNODB STATUS after changes to verify the expected effect.

  5. Keep a versioned backup of my.cnf — a bad configuration can prevent MySQL from starting.

  6. Run mysqltuner.pl regularly in production and review its recommendations after traffic changes.