Skip to main content

MySQL Database Server on Ubuntu

Trademark Notice

MySQL® is a registered trademark of Oracle Corporation and/or its affiliates. All trademarks are used for representation purposes only. This content is for educational purposes only.

Complete guide to installing and configuring MySQL on Ubuntu Server.

Installation

MySQL 8.0 Installation

# Update package list
sudo apt update

# Install MySQL server
sudo apt install -y mysql-server

# Start and enable MySQL
sudo systemctl start mysql
sudo systemctl enable mysql

# Check status
sudo systemctl status mysql

# Check version
mysql --version

Secure Installation

# Run security script
sudo mysql_secure_installation

Follow prompts:

  • Set root password
  • Remove anonymous users: Yes
  • Disallow root login remotely: Yes
  • Remove test database: Yes
  • Reload privilege tables: Yes

Initial Configuration

Access MySQL

# Login as root
sudo mysql

# Or with password
mysql -u root -p

Create Database and User

-- Create database
CREATE DATABASE myapp_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- Create user
CREATE USER 'myapp_user'@'localhost' IDENTIFIED BY 'strong_password';

-- Grant privileges
GRANT ALL PRIVILEGES ON myapp_db.* TO 'myapp_user'@'localhost';

-- Apply changes
FLUSH PRIVILEGES;

-- Verify
SHOW DATABASES;
SELECT User, Host FROM mysql.user;

-- Exit
EXIT;

Configure Remote Access

# Edit MySQL configuration
sudo nano /etc/mysql/mysql.conf.d/mysqld.cnf
# Change bind-address
bind-address = 0.0.0.0

# Or specific IP
bind-address = 192.168.1.100
-- Create remote user
CREATE USER 'remote_user'@'%' IDENTIFIED BY 'strong_password';
GRANT ALL PRIVILEGES ON myapp_db.* TO 'remote_user'@'%';

-- Or specific IP
CREATE USER 'remote_user'@'192.168.1.0/255.255.255.0' IDENTIFIED BY 'strong_password';
GRANT ALL PRIVILEGES ON myapp_db.* TO 'remote_user'@'192.168.1.%';

FLUSH PRIVILEGES;
# Open firewall
sudo ufw allow 3306/tcp

# Restart MySQL
sudo systemctl restart mysql

Configuration Files

/etc/mysql/
├── my.cnf # Main config (includes others)
├── mysql.cnf # MySQL client config
└── mysql.conf.d/
└── mysqld.cnf # MySQL server config

Main Configuration

# Edit server configuration
sudo nano /etc/mysql/mysql.conf.d/mysqld.cnf
[mysqld]
# Basic Settings
user = mysql
pid-file = /var/run/mysqld/mysqld.pid
socket = /var/run/mysqld/mysqld.sock
port = 3306
basedir = /usr
datadir = /var/lib/mysql
tmpdir = /tmp

# Character Set
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci

# Network
bind-address = 127.0.0.1
max_connections = 200

# Query Cache (disabled in MySQL 8.0+)
# query_cache_type = 1
# query_cache_size = 16M

# InnoDB Settings
innodb_buffer_pool_size = 1G
innodb_log_file_size = 256M
innodb_flush_method = O_DIRECT
innodb_flush_log_at_trx_commit = 2

# Logging
log_error = /var/log/mysql/error.log
slow_query_log = 1
slow_query_log_file = /var/log/mysql/mysql-slow.log
long_query_time = 2

# Binary Logging (for replication)
server-id = 1
log_bin = /var/log/mysql/mysql-bin.log
expire_logs_days = 7
max_binlog_size = 100M

# Security
local-infile = 0
# Restart MySQL
sudo systemctl restart mysql

User Management

-- Create user
CREATE USER 'username'@'localhost' IDENTIFIED BY 'password';

-- Change password
ALTER USER 'username'@'localhost' IDENTIFIED BY 'new_password';

-- Grant specific privileges
GRANT SELECT, INSERT, UPDATE, DELETE ON database.* TO 'username'@'localhost';

-- Grant all privileges
GRANT ALL PRIVILEGES ON database.* TO 'username'@'localhost';

-- Grant with grant option
GRANT ALL PRIVILEGES ON database.* TO 'username'@'localhost' WITH GRANT OPTION;

-- Revoke privileges
REVOKE INSERT, UPDATE ON database.* FROM 'username'@'localhost';

-- Drop user
DROP USER 'username'@'localhost';

-- Show grants
SHOW GRANTS FOR 'username'@'localhost';

-- List all users
SELECT User, Host, authentication_string FROM mysql.user;

Database Operations

-- Create database
CREATE DATABASE dbname CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- List databases
SHOW DATABASES;

-- Use database
USE dbname;

-- Drop database
DROP DATABASE dbname;

-- Show tables
SHOW TABLES;

-- Describe table
DESCRIBE tablename;
SHOW CREATE TABLE tablename;

-- Show table size
SELECT
table_schema AS 'Database',
table_name AS 'Table',
ROUND((data_length + index_length) / 1024 / 1024, 2) AS 'Size (MB)'
FROM information_schema.tables
WHERE table_schema = 'dbname'
ORDER BY (data_length + index_length) DESC;

Backup and Restore

Full Database Backup

# Backup single database
mysqldump -u root -p dbname > backup_dbname_$(date +%Y%m%d).sql

# Backup with compression
mysqldump -u root -p dbname | gzip > backup_dbname_$(date +%Y%m%d).sql.gz

# Backup all databases
mysqldump -u root -p --all-databases > backup_all_$(date +%Y%m%d).sql

# Backup specific tables
mysqldump -u root -p dbname table1 table2 > backup_tables_$(date +%Y%m%d).sql

# Backup structure only
mysqldump -u root -p --no-data dbname > structure_$(date +%Y%m%d).sql

# Backup data only
mysqldump -u root -p --no-create-info dbname > data_$(date +%Y%m%d).sql

Restore Database

# Restore database
mysql -u root -p dbname < backup_dbname.sql

# Restore from compressed backup
gunzip < backup_dbname.sql.gz | mysql -u root -p dbname

# Restore all databases
mysql -u root -p < backup_all.sql

# Create database and restore
mysql -u root -p -e "CREATE DATABASE dbname"
mysql -u root -p dbname < backup_dbname.sql

Automated Backup Script

# Create backup script
sudo nano /usr/local/bin/mysql-backup.sh
#!/bin/bash

# Configuration
BACKUP_DIR="/backup/mysql"
MYSQL_USER="root"
MYSQL_PASSWORD="your_password"
RETENTION_DAYS=7

# Create backup directory
mkdir -p $BACKUP_DIR

# Backup filename
BACKUP_FILE="$BACKUP_DIR/mysql_backup_$(date +%Y%m%d_%H%M%S).sql.gz"

# Perform backup
mysqldump -u $MYSQL_USER -p$MYSQL_PASSWORD --all-databases | gzip > $BACKUP_FILE

# Remove old backups
find $BACKUP_DIR -name "mysql_backup_*.sql.gz" -mtime +$RETENTION_DAYS -delete

# Log
echo "Backup completed: $BACKUP_FILE"
# Make executable
sudo chmod +x /usr/local/bin/mysql-backup.sh

# Add to cron (daily at 2 AM)
sudo crontab -e
0 2 * * * /usr/local/bin/mysql-backup.sh >> /var/log/mysql-backup.log 2>&1

Replication Setup

Master Configuration

# Edit master config
sudo nano /etc/mysql/mysql.conf.d/mysqld.cnf
[mysqld]
server-id = 1
log_bin = /var/log/mysql/mysql-bin.log
binlog_do_db = myapp_db
bind-address = 0.0.0.0
-- Create replication user
CREATE USER 'repl_user'@'%' IDENTIFIED BY 'repl_password';
GRANT REPLICATION SLAVE ON *.* TO 'repl_user'@'%';
FLUSH PRIVILEGES;

-- Get master status
SHOW MASTER STATUS;
-- Note: File and Position values

Slave Configuration

# Edit slave config
sudo nano /etc/mysql/mysql.conf.d/mysqld.cnf
[mysqld]
server-id = 2
relay-log = /var/log/mysql/mysql-relay-bin.log
log_bin = /var/log/mysql/mysql-bin.log
binlog_do_db = myapp_db
-- Configure slave
CHANGE MASTER TO
MASTER_HOST='192.168.1.100',
MASTER_USER='repl_user',
MASTER_PASSWORD='repl_password',
MASTER_LOG_FILE='mysql-bin.000001',
MASTER_LOG_POS=154;

-- Start slave
START SLAVE;

-- Check slave status
SHOW SLAVE STATUS\G

-- Verify
-- Look for: Slave_IO_Running: Yes
-- Slave_SQL_Running: Yes

Performance Tuning

InnoDB Optimization

# Calculate buffer pool size (50-70% of RAM)
# For 8GB RAM: 4-5GB buffer pool

sudo nano /etc/mysql/mysql.conf.d/mysqld.cnf
[mysqld]
# InnoDB Buffer Pool
innodb_buffer_pool_size = 4G
innodb_buffer_pool_instances = 4

# InnoDB Log
innodb_log_file_size = 512M
innodb_log_buffer_size = 16M
innodb_flush_log_at_trx_commit = 2
innodb_flush_method = O_DIRECT

# InnoDB File
innodb_file_per_table = 1
innodb_open_files = 400

# InnoDB Thread
innodb_thread_concurrency = 0
innodb_read_io_threads = 4
innodb_write_io_threads = 4

# InnoDB Other
innodb_io_capacity = 2000
innodb_io_capacity_max = 4000

Connection and Cache Tuning

[mysqld]
max_connections = 200
max_connect_errors = 100
max_allowed_packet = 64M
thread_cache_size = 50
table_open_cache = 4000
table_definition_cache = 400

# Temp Tables
tmp_table_size = 64M
max_heap_table_size = 64M

# Sort Buffer
sort_buffer_size = 2M
read_buffer_size = 2M
read_rnd_buffer_size = 4M
join_buffer_size = 2M

Query Cache (MySQL 5.7 only)

# Not available in MySQL 8.0+
[mysqld]
query_cache_type = 1
query_cache_size = 128M
query_cache_limit = 2M

Monitoring

Check MySQL Status

-- Show status variables
SHOW STATUS;
SHOW GLOBAL STATUS;

-- Show variables
SHOW VARIABLES;
SHOW GLOBAL VARIABLES LIKE '%buffer%';

-- Show processes
SHOW PROCESSLIST;
SHOW FULL PROCESSLIST;

-- InnoDB status
SHOW ENGINE INNODB STATUS\G

-- Table status
SHOW TABLE STATUS FROM dbname;

-- Check connections
SELECT user, host, db, command, time, state
FROM information_schema.processlist;

Slow Query Log

-- Enable slow query log
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 2;

-- Check slow query log
SHOW VARIABLES LIKE 'slow_query%';
# Analyze slow queries
sudo mysqldumpslow /var/log/mysql/mysql-slow.log

# Top 10 slowest queries
sudo mysqldumpslow -s t -t 10 /var/log/mysql/mysql-slow.log

Performance Schema

-- Enable performance schema
SET GLOBAL performance_schema = ON;

-- View top queries by execution time
SELECT * FROM sys.statement_analysis
ORDER BY total_latency DESC
LIMIT 10;

-- View table with most I/O
SELECT * FROM sys.io_global_by_file_by_bytes
LIMIT 10;

Security

Security Best Practices

-- Remove anonymous users
DELETE FROM mysql.user WHERE User='';

-- Remove remote root
DELETE FROM mysql.user WHERE User='root' AND Host NOT IN ('localhost', '127.0.0.1', '::1');

-- Remove test database
DROP DATABASE IF EXISTS test;
DELETE FROM mysql.db WHERE Db='test' OR Db='test\\_%';

FLUSH PRIVILEGES;

Password Policy (MySQL 8.0)

-- Check password policy
SHOW VARIABLES LIKE 'validate_password%';

-- Set password policy
SET GLOBAL validate_password.policy = MEDIUM;
SET GLOBAL validate_password.length = 12;
SET GLOBAL validate_password.mixed_case_count = 1;
SET GLOBAL validate_password.number_count = 1;
SET GLOBAL validate_password.special_char_count = 1;

SSL/TLS Encryption

# Generate SSL certificates
sudo mysql_ssl_rsa_setup --uid=mysql

# Verify SSL
mysql -u root -p -e "SHOW VARIABLES LIKE '%ssl%';"
-- Require SSL for user
ALTER USER 'username'@'%' REQUIRE SSL;

-- Check SSL status
SHOW STATUS LIKE 'Ssl_cipher';

Troubleshooting

# Check MySQL error log
sudo tail -f /var/log/mysql/error.log

# Check if MySQL is running
sudo systemctl status mysql

# Check MySQL port
sudo netstat -tlnp | grep 3306

# Test connection
mysql -u root -p -h 127.0.0.1 -e "SELECT 1"

# Check disk space
df -h /var/lib/mysql

# Check MySQL process
ps aux | grep mysql

# Repair tables
sudo mysqlcheck -u root -p --auto-repair --all-databases

# Optimize tables
sudo mysqlcheck -u root -p --optimize --all-databases

Common Issues

MySQL won't start:

# Check error log
sudo tail -100 /var/log/mysql/error.log

# Check permissions
sudo chown -R mysql:mysql /var/lib/mysql

# Check disk space
df -h

# Try starting in safe mode
sudo mysqld_safe --skip-grant-tables &

Connection refused:

# Check if MySQL is running
sudo systemctl status mysql

# Check port
sudo netstat -tlnp | grep 3306

# Check firewall
sudo ufw status

Best Practices

  1. Regular Backups: Automate daily backups
  2. Security: Use strong passwords, limit remote access
  3. Monitoring: Enable slow query log
  4. Updates: Keep MySQL updated
  5. Replication: Set up master-slave for high availability
  6. Optimization: Tune InnoDB buffer pool
  7. Indexing: Create proper indexes for queries
  8. Connection Pooling: Use connection pooling in applications
  9. SSL/TLS: Encrypt connections
  10. Documentation: Document schema changes

Resources