In this tutorial, you’ll install MySQL 8.0 on Ubuntu 24.04 from Ubuntu’s default repository, verify the service, keep the administrative root account on secure socket authentication, run the MySQL hardening utility, create a dedicated application database and user, complete an authenticated CRUD test, and keep port 3306 private.
MySQL is a relational database server commonly used by WordPress, Laravel, PHP applications, e-commerce platforms, and other transactional workloads. Ubuntu 24.04 provides MySQL 8.0 through its supported repositories, so installation and security updates are handled through APT.
Raff supports 3,000+ customers and 15,000+ VMs in its us-east region. Raff Linux VMs provide KVM virtualization, full root access, NVMe storage, and 3 Gbps unmetered bandwidth for self-hosted database workloads.
Prerequisites:
- A Raff Linux VM running Ubuntu 24.04
- SSH access with a user that has sudo privileges
- At least 2 GB RAM for development or light workloads; 4 GB or more is a better production starting point
- A backup plan before modifying an existing MySQL installation
- Port
22/tcpavailable for SSH administration
📌 Local-first design: This tutorial keeps MySQL bound to the loopback interface. Applications on the same VM can connect locally, while port
3306remains unavailable from the public internet.
The original workflow was tested on a Raff VM with 2 vCPU and 4 GB RAM. The package, account, and network guidance was reviewed against current Canonical and MySQL 8.0 documentation in July 2026.
Step 1 — Update Ubuntu and install MySQL
Update the package index and install current system updates:
sudo apt update sudo apt upgrade -y
Install MySQL Server and UFW from Ubuntu’s repositories:
sudo apt install -y mysql-server ufw
Canonical documents the same package installation method in its Ubuntu Server MySQL guide.
Verify the installed package and server version:
mysql --version sudo mysql -NBe 'SELECT VERSION();'
Expected output follows this format:
mysql Ver 8.0.x 8.0.x-0ubuntu0.24.04.x
The exact patch revision changes as Ubuntu publishes maintenance and security updates.
Step 2 — Verify the MySQL service
MySQL should start automatically after installation. Confirm that the service is active:
systemctl is-active mysql
Expected output:
active
Confirm that MySQL starts at boot:
systemctl is-enabled mysql
Expected output:
enabled
View the full service state without opening an interactive pager:
sudo systemctl status mysql --no-pager
Expected output includes:
Active: active (running)
Verify that the server responds through its local Unix socket:
sudo mysqladmin ping
Expected output:
mysqld is alive
Check the active TCP listener:
sudo ss -lntp | grep ':3306'
Expected output includes the loopback address:
127.0.0.1:3306
Step 3 — Verify root socket authentication
On Ubuntu 24.04, the MySQL root account uses the auth_socket plugin by default. You access the administrative account with sudo mysql; no MySQL root password is required because MySQL verifies the local Linux root identity through the Unix socket.
Open the MySQL client:
sudo mysql
Expected prompt:
mysql>
Verify the root account’s host and authentication plugin:
SELECT user, host, plugin FROM mysql.user WHERE user = 'root';
Expected output includes:
root | localhost | auth_socket
Exit the client:
EXIT;
Keep root@localhost on auth_socket unless you have a documented operational requirement to change it. Applications must never use the root database account.
⚠️ Important: Do not convert the root account to password authentication merely to connect an application or web-based database tool. Create a separate, least-privileged account for that purpose.
Step 4 — Run mysql_secure_installation
MySQL includes a hardening utility that can remove anonymous accounts, block remote root access, remove the test database, and optionally enable password-strength validation.
Run it locally with sudo:
sudo mysql_secure_installation
The exact prompts can vary with the Ubuntu package revision and current server state. Recommended answers for a new server are:
- Enable the password validation component when you want MySQL to enforce password complexity
- Remove anonymous users:
Y - Disallow root login remotely:
Y - Remove the test database:
Y - Reload privilege tables:
Y
Ubuntu’s socket-authenticated root account may cause the utility to skip setting a root password. This is expected and should not be treated as an error.
Verify that no anonymous accounts remain:
sudo mysql -NBe \ "SELECT user, host FROM mysql.user WHERE user = '';"
The command should return no rows.
Verify that the test database is absent:
sudo mysql -NBe \ "SELECT schema_name FROM information_schema.schemata WHERE schema_name = 'test';"
The command should return no rows.
MySQL documents the hardening utility in its mysql_secure_installation reference.
Step 5 — Create an application database and user
Create a separate database account for the application. The account will have privileges only on its own database.
Open the administrative client:
sudo mysql
Create the application database with MySQL 8.0’s UTF-8 character set and collation:
CREATE DATABASE raffapp CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
Create a local application user with a generated password:
CREATE USER 'raffappuser'@'localhost' IDENTIFIED WITH caching_sha2_password BY RANDOM PASSWORD;
MySQL returns the generated password in the result. Copy it immediately into your secrets manager or another protected credential store. It cannot be recovered in plain text later.
Grant privileges only on the application database:
GRANT ALL PRIVILEGES ON raffapp.* TO 'raffappuser'@'localhost';
Verify the account properties and grants:
SHOW CREATE USER 'raffappuser'@'localhost'; SHOW GRANTS FOR 'raffappuser'@'localhost';
Expected output includes:
caching_sha2_password GRANT ALL PRIVILEGES ON `raffapp`.* TO `raffappuser`@`localhost`
Exit the administrative client:
EXIT;
CREATE USER and GRANT take effect immediately, so a manual FLUSH PRIVILEGES is not required.
Step 6 — Verify the application login
Connect to the new database as the application user:
mysql -u raffappuser -p raffapp
Enter the generated application password when prompted.
Verify the authenticated identity and selected database:
SELECT CURRENT_USER(), DATABASE();
Expected output includes:
raffappuser@localhost | raffapp
Confirm that the account cannot access unrelated application databases:
SHOW DATABASES;
The account can see system schemas required by MySQL and the raffapp database, but it should not have privileges on other application databases.
Exit the client:
EXIT;
Step 7 — Run an authenticated CRUD test
Connect as the application user:
mysql -u raffappuser -p raffapp
Create a temporary table:
CREATE TABLE tutorial_check ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, name VARCHAR(100) NOT NULL, status VARCHAR(32) NOT NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY unique_name (name) ) ENGINE=InnoDB;
Insert a row:
INSERT INTO tutorial_check (name, status) VALUES ('raff-mysql-test', 'created');
Read it:
SELECT name, status FROM tutorial_check WHERE name = 'raff-mysql-test';
Expected output:
raff-mysql-test | created
Update the row:
UPDATE tutorial_check SET status = 'verified' WHERE name = 'raff-mysql-test';
Verify the update:
SELECT name, status FROM tutorial_check WHERE name = 'raff-mysql-test';
Expected output:
raff-mysql-test | verified
Delete the row and remove the temporary table:
DELETE FROM tutorial_check WHERE name = 'raff-mysql-test'; DROP TABLE tutorial_check;
Exit the client:
EXIT;
The database is working end to end when the application user can authenticate and create, read, update, and delete data in raffapp.
Step 8 — Keep MySQL local and protect port 3306
Verify the server’s configured bind address and port:
sudo mysql -NBe \ "SELECT @@bind_address, @@port;"
Expected output:
127.0.0.1 3306
Confirm the active socket:
sudo ss -lntp | grep ':3306'
Expected output includes only the loopback address:
127.0.0.1:3306
Allow SSH before enabling UFW so you do not lock yourself out:
sudo ufw allow OpenSSH
If the OpenSSH profile is unavailable, allow the SSH port directly:
sudo ufw allow 22/tcp
Add a deny rule for MySQL as defense in depth and enable UFW:
sudo ufw deny 3306/tcp sudo ufw --force enable
Verify the firewall rules:
sudo ufw status numbered
Expected output includes:
OpenSSH ALLOW IN 3306/tcp DENY IN
📌 Important: The UFW rule does not replace
bind-address = 127.0.0.1. The bind address prevents MySQL from listening on public interfaces; the firewall supplies a second control.
For an application and database running on the same VM, no inbound MySQL rule is required.
Step 9 — Optional: allow one private application server
Use this section only when the application runs on a separate VM connected through a private network. Do not expose MySQL to the public internet.
Identify the database VM’s private IP and the application VM’s private IP. The examples below use:
Database private IP: 10.0.0.5 Application private IP: 10.0.0.10
Back up the MySQL configuration:
sudo cp /etc/mysql/mysql.conf.d/mysqld.cnf \ /etc/mysql/mysql.conf.d/mysqld.cnf.before-private-network
Edit the server configuration:
sudo nano /etc/mysql/mysql.conf.d/mysqld.cnf
Set bind-address to both loopback and the database VM’s specific private IP:
bind-address = 127.0.0.1,10.0.0.5
MySQL 8.0 supports comma-separated bind addresses. Do not use 0.0.0.0 when a specific private interface is available.
Test the configuration and restart MySQL:
sudo mysqld --validate-config sudo systemctl restart mysql systemctl is-active mysql
Create a separate account restricted to the application VM’s exact private IP and require encrypted transport:
sudo mysql
CREATE USER 'raffappuser'@'10.0.0.10' IDENTIFIED WITH caching_sha2_password BY RANDOM PASSWORD REQUIRE SSL; GRANT ALL PRIVILEGES ON raffapp.* TO 'raffappuser'@'10.0.0.10'; SHOW GRANTS FOR 'raffappuser'@'10.0.0.10'; EXIT;
Store the generated password securely. Allow only the application VM through UFW:
sudo ufw delete deny 3306/tcp sudo ufw allow from 10.0.0.10 to 10.0.0.5 port 3306 proto tcp sudo ufw status numbered
From the application VM, test the encrypted private connection:
mysql \ --host=10.0.0.5 \ --user=raffappuser \ --password \ --ssl-mode=REQUIRED \ raffapp
After verification, confirm that the database port is still unavailable through the public IP. Review public vs private traffic before separating application and database servers.
Step 10 — Run the final verification
Run the final service, account, network, and firewall checks:
echo "MySQL service:" systemctl is-active mysql echo "Boot status:" systemctl is-enabled mysql echo "Version:" sudo mysql -NBe 'SELECT VERSION();' echo "Root authentication:" sudo mysql -NBe \ "SELECT user, host, plugin FROM mysql.user WHERE user = 'root';" echo "Application account:" sudo mysql -NBe \ "SELECT user, host, plugin FROM mysql.user WHERE user = 'raffappuser';" echo "Listening socket:" sudo ss -lntp | grep ':3306' echo "Firewall:" sudo ufw status numbered
The installation is complete when:
- MySQL 8.0 is installed and the service is active and enabled
root@localhostusesauth_socket- Anonymous users and the test database are absent
raffappuserusescaching_sha2_password- The application user can complete authenticated CRUD operations
- MySQL listens only on loopback, or on loopback plus an approved private IP
- UFW blocks public access to port
3306