Linux distributions often ship MariaDB instead of MySQL, or a MySQL version frozen for the whole life of the distribution. If you want MySQL 8.4 LTS, the Long Term Support release with 8 years of security fixes, Oracle's official repository is the path to prefer.
This guide takes you from A to Z: adding the repository, installing the packages, checking the service, initial hardening (including restricting the network listening) and the first connection. In a few minutes you will have a working MySQL instance, ready for the other guides in this section.
What you will learn
- Add Oracle's official APT or YUM repository and install MySQL 8.4 LTS
- Understand the difference between the Debian installation (password set during installation) and the RHEL one (temporary password generated)
- Check that the service is active with
systemctland connect with themysqlclient - Secure the installation with
mysql_secure_installation(remove the anonymous user, disable remote root access) - Restrict the network listening of
mysqld, which listens on all interfaces after installation - Identify the configuration files and where they live depending on the distribution
In what context?
This guide is your entry point if you are in one of these situations:
- You are setting up a server for development or testing and want MySQL (not MariaDB)
- You need to standardise the MySQL version across several machines (Debian + RHEL in the same estate)
- You are preparing a lab to follow the configuration, backup or replication guides of this section
- Your distribution ships MariaDB by default and you want to install Oracle's official MySQL
- You are deploying MySQL for the first time and want to understand what the installation creates on the system
What this guide does not cover
Installation lays the foundations; the rest has its own guides. To understand the architecture (instance, InnoDB, buffer pool) and the vocabulary before administering, read Discover MySQL. To tune memory, connections and logging, go to the configuration guide. For dedicated accounts, TLS, roles and least privilege, see security. And to use the mysql client day to day (exploration, queries, import and export), read Getting started with the mysql client.
Prerequisites
- A Debian 12 or 13, Ubuntu 22.04+, Rocky Linux 9 or RHEL 9 server with root or sudo access
- An Internet connection (to download the packages from Oracle's repositories)
- No MariaDB version installed, because the packages conflict. If MariaDB is present, uninstall it first (see the Troubleshooting section)
MySQL vs MariaDB: clarification
Many Linux distributions (Debian, Rocky Linux, Fedora) ship MariaDB instead of MySQL in their default repositories. MariaDB is a fork of MySQL launched in 2009 by Michael Widenius, MySQL's original founder, while Oracle's acquisition of Sun Microsystems was under way: Sun owned MySQL, the merger agreement dates from 19 April 2009 and Oracle completed the deal on 26 January 2010, according to the Form 8-K Oracle filed with the SEC.
| MySQL (Oracle) | MariaDB | |
|---|---|---|
| Publisher | Oracle Corporation | MariaDB Foundation / MariaDB plc |
| Current LTS version | 8.4 (8-year support) | 12.3, the latest (11.8, 11.4 and 10.11 remain LTS), as of 2026-10-20 |
| Default engine | InnoDB | InnoDB, with Aria for system tables |
| Replication | GTID, Group Replication, InnoDB Cluster | GTID (different format), Galera Cluster |
| Default auth plugin | caching_sha2_password | mysql_native_password |
| Performance Schema | Complete | Partial (differences) |
A word about Aria, which you meet quickly on the MariaDB side: it is the engine written to replace MyISAM while surviving crashes, and it holds MariaDB's system tables. It does not derive from InnoDB, which remains the engine for application tables on both sides.
The two systems share the network protocol and much of the SQL syntax, but they are not interchangeable in production. The specific extensions (Group Replication vs Galera, advanced Performance Schema, MySQL Shell) are not compatible.
This guide is about Oracle's official MySQL. If you want MariaDB, the installation commands are different.
Install from Oracle's official repositories
-
Download Oracle's repository configuration package
The
mysql-apt-configpackage adds Oracle's APT sources and GPG keys. Download the current version from the official APT page:Terminal window cd /tmpwget https://dev.mysql.com/get/mysql-apt-config_0.8.36-1_all.deb -
Install the configuration package
For a non-interactive installation that directly selects MySQL 8.4 LTS:
Terminal window sudo DEBIAN_FRONTEND=noninteractive dpkg -i mysql-apt-config_0.8.36-1_all.debThis package creates the file
/etc/apt/sources.list.d/mysql.listwith the right URL and Oracle's GPG key. -
Update the sources and install MySQL
Terminal window sudo apt updatesudo apt install -y mysql-serverThe installation automatically pulls in the dependencies:
mysql-client,mysql-common,mysql-community-server,mysql-community-client. -
Check that the service is active
Terminal window sudo systemctl status mysql● mysql.service - MySQL Community ServerLoaded: loaded (/lib/systemd/system/mysql.service; enabled; preset: enabled)Active: active (running) since Mon 2026-04-13 08:49:23 UTC; 1min agoDocs: man:mysqld(8)http://dev.mysql.com/doc/refman/en/using-systemd.htmlProcess: 4498 ExecStartPre=/usr/share/mysql/mysql-systemd-start pre (code=exited, status=0/SUCCESS)Main PID: 4521 (/usr/sbin/mysqld)Status: "Server is operational"Tasks: 39 (limit: 2314)Memory: 364.2MCPU: 3.241sCGroup: /system.slice/mysql.service└─4521 /usr/sbin/mysqld
-
Install the official MySQL YUM repository
Terminal window sudo dnf install -y https://dev.mysql.com/get/mysql84-community-release-el9-3.noarch.rpmThis RPM adds the repo file
/etc/yum.repos.d/mysql-community.repoand Oracle's GPG keys. -
Install MySQL
Terminal window sudo dnf install -y mysql-community-server -
Start and enable the service
Unlike Debian, the installation on RHEL does not start the service automatically:
Terminal window sudo systemctl enable --now mysqld -
Retrieve the temporary root password
On RHEL/Rocky, MySQL generates a temporary root password at first startup, written to the error log:
Terminal window sudo grep 'temporary password' /var/log/mysqld.log2026-04-13T08:49:28.123456Z 6 [Note] [MY-010454] [Server] A temporary password is generated for root@localhost: Xk8#fPz!2qLmWrite this password down, you will need it for the first connection.
-
Change the root password
Connect with the temporary password and change it immediately:
Terminal window mysql -u root -pALTER USER 'root'@'localhost' IDENTIFIED BY 'YourPassword!2026';The sample password carries a digit and a special character, and that is not decoration. On RPM installations, the
validate_passwordcomponent is already active with theMEDIUMpolicy by default, which requires at least eight characters, a lowercase letter, an uppercase letter, a digit and a non-alphanumeric character. A simple password is rejected withERROR 1819 (HY000): Your password does not satisfy the current policy requirements.
Check the installation
The systemd service
The MySQL service is called mysql on Debian/Ubuntu and mysqld on RHEL/Rocky:
# Service statesudo systemctl status mysql
# Stop / start / restartsudo systemctl stop mysqlsudo systemctl start mysqlsudo systemctl restart mysql
# Enable start at bootsudo systemctl enable mysql# Service statesudo systemctl status mysqld
# Stop / startsudo systemctl stop mysqldsudo systemctl start mysqld
# Enable start at bootsudo systemctl enable mysqldDo not confuse an active service with an enabled one. Active (active (running) in systemctl status) means it is running right now. Enabled (enabled, visible on the Loaded: line and through systemctl is-enabled mysql, or mysqld on RHEL/Rocky) means it will be started at the next boot. Only the second guarantees that the instance comes back after the machine restarts.
The installed packages
Here is what the Oracle installation puts on the system (Debian):
dpkg -l 'mysql*' | grep '^ii'ii mysql-apt-config 0.8.36-1 all Auto configuration for MySQL APT Repo.ii mysql-client 8.4.11-1debian12 amd64 MySQL Client meta package for v8.4ii mysql-common 8.4.11-1debian12 amd64 MySQL Commonii mysql-community-client 8.4.11-1debian12 amd64 MySQL Clientii mysql-community-server 8.4.11-1debian12 amd64 MySQL Serverii mysql-server 8.4.11-1debian12 amd64 MySQL Server meta package for v8.4| Package | Contents |
|---|---|
mysql-community-server | The mysqld server, the configuration files, the systemd scripts |
mysql-community-client | The client tools: mysql, mysqldump, mysqladmin, mysqlbinlog |
mysql-common | Shared configuration files |
mysql-apt-config | Management of the Oracle repository (version selection) |
The configuration files
The location of the configuration depends on the distribution:
| Distribution | Server configuration | Datadir |
|---|---|---|
| Debian / Ubuntu | /etc/mysql/mysql.conf.d/mysqld.cnf | /var/lib/mysql/ |
| RHEL / Rocky | /etc/my.cnf | /var/lib/mysql/ |
MySQL reads its configuration files in a precise order:
mysqld --verbose --help 2>/dev/null | grep -A1 "Default options"Default options are read from the following files in the given order:/etc/my.cnf /etc/mysql/my.cnf ~/.my.cnfOn Debian, the main file /etc/mysql/my.cnf includes the files of /etc/mysql/conf.d/ and /etc/mysql/mysql.conf.d/:
/etc/mysql/├── my.cnf → Main file (includes conf.d/ and mysql.conf.d/)├── conf.d/│ └── mysql.cnf → mysql client options└── mysql.conf.d/ └── mysqld.cnf → mysqld server configurationHere is the default content of mysqld.cnf (the essential parameters):
grep -v '^#\|^$' /etc/mysql/mysql.conf.d/mysqld.cnf[mysqld]pid-file = /var/run/mysqld/mysqld.pidsocket = /var/run/mysqld/mysqld.sockdatadir = /var/lib/mysqllog-error = /var/log/mysql/error.logThis file contains no bind-address line, not even a commented one: the server therefore keeps its default value and listens on all interfaces. Oracle's RPM packages (/etc/my.cnf) behave the same way. The "How do you restrict MySQL's network listening?" section further down shows how to fix this.
Connect with the mysql client
The local connection goes through a Unix socket, which is the default behaviour when you do not specify -h. If you left the root password empty (auth_socket), replace the command below with sudo mysql:
mysql -u root -pEnter password:Welcome to the MySQL monitor. Commands end with ; or \g.Your MySQL connection id is 8Server version: 8.4.11 MySQL Community Server - GPL
Copyright (c) 2000, 2026, Oracle and/or its affiliates.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql>Check the version and the connection:
SELECT VERSION();+-----------+| VERSION() |+-----------+| 8.4.11 |+-----------+\s--------------mysql Ver 8.4.11 for Linux on x86_64 (MySQL Community Server - GPL)
Connection id: 8Current database:Current user: root@localhostSSL: Not in useCurrent pager: stdoutUsing outfile: ''Using delimiter: ;Server version: 8.4.11 MySQL Community Server - GPLProtocol version: 10Connection: Localhost via UNIX socketServer characterset: utf8mb4Db characterset: utf8mb4Client characterset: utf8mb4Conn. characterset: utf8mb4UNIX socket: /var/run/mysqld/mysqld.sockBinary data as: HexadecimalUptime: 15 min 32 secConnection: Localhost via UNIX socket confirms that the connection goes through the socket, with no network.
Check the key parameters after installation
SELECT @@innodb_buffer_pool_size / 1024 / 1024 AS buffer_pool_mb, @@max_connections, @@bind_address, @@log_bin, @@gtid_mode;+----------------+-----------------+--------------+---------+-----------+| buffer_pool_mb | max_connections | bind_address | log_bin | gtid_mode |+----------------+-----------------+--------------+---------+-----------+| 128 | 151 | * | 1 | OFF |+----------------+-----------------+--------------+---------+-----------+| Parameter | Default value | What it means |
|---|---|---|
innodb_buffer_pool_size | 128 MB | InnoDB memory cache, far too small for production |
max_connections | 151 | Maximum of 151 simultaneous connections |
bind_address | * | All interfaces: MySQL accepts network connections (see "How do you restrict MySQL's network listening?") |
log_bin | ON (MySQL 8.4) | The binary log is enabled by default |
gtid_mode | OFF | GTID is disabled by default, to be enabled for replication |
Above all, remember the bind_address line: the value * means that mysqld listens on all network interfaces, not only on the local machine. The "How do you restrict MySQL's network listening?" section further down shows how to check this and fix it.
Now check the authentication plugin of the existing accounts:
SELECT user, host, plugin FROM mysql.user WHERE user = 'root';+------+-----------+-----------------------+| user | host | plugin |+------+-----------+-----------------------+| root | localhost | caching_sha2_password |+------+-----------+-----------------------+Secure the initial installation
mysql_secure_installation
MySQL provides an interactive script that applies the first security measures:
sudo mysql_secure_installationThe script asks several questions depending on your installation method. On RPM/YUM installations, the validate_password component is often already installed and enabled at first startup, so the corresponding question does not appear.
| Question | Recommended answer | Why |
|---|---|---|
| Setup VALIDATE PASSWORD component? | Yes (if offered) | Enables the password complexity policy |
| Password validation policy | MEDIUM (1) | Requires length + uppercase + digits + special characters |
| Change root password? | No (if already changed) | Pointless if you just set it during installation |
| Remove anonymous users? | Yes | Removes the nameless accounts that allow a connection without authentication |
| Disallow root login remotely? | Yes | Prevents root from connecting from another server |
| Remove test database? | Yes | Removes the test database that anyone can access |
| Reload privilege tables? | Yes | Applies the changes immediately |
After running it, check that no anonymous account is left:
SELECT user, host, plugin FROM mysql.user;+------------------+-----------+-----------------------+| user | host | plugin |+------------------+-----------+-----------------------+| mysql.infoschema | localhost | caching_sha2_password || mysql.session | localhost | caching_sha2_password || mysql.sys | localhost | caching_sha2_password || root | localhost | caching_sha2_password |+------------------+-----------+-----------------------+Three system accounts (mysql.infoschema, mysql.session, mysql.sys) and the root@localhost account, no anonymous account, no root@% (remote) account. This check protects at the account level: it does not close the network, which is what the next section does.
How do you restrict MySQL's network listening?
After an installation from the Oracle packages (.deb or .rpm), mysqld listens on all interfaces: the shipped configuration file contains no bind-address line, and the default value of the bind_address variable is * (MySQL 8.4 documentation, "Server System Variables" page). The TCP 3306 (classic protocol) and TCP 33060 (X protocol) ports are therefore reachable from the network, as long as no firewall blocks them. This section is about the Oracle repository used by this guide.
Start by observing the actual state. The ss command lists the listening sockets (-t TCP, -l listening, -n numeric values, -p process, which requires sudo):
sudo ss -tlnp | grep mysqldYou get two LISTEN lines for the mysqld process, on ports 3306 and 33060. If the local address is *, 0.0.0.0 or [::] instead of 127.0.0.1, the server is open to all interfaces.
Two measures combine. The first is a firewall that only allows port 3306 from the addresses that need it: check what is active with sudo ufw status (Debian/Ubuntu) or sudo firewall-cmd --list-all (RHEL/Rocky). The second closes the door at the source with bind-address. If your applications run on the same machine as MySQL, add these two lines to the [mysqld] section of /etc/mysql/mysql.conf.d/mysqld.cnf (Debian/Ubuntu) or of /etc/my.cnf (RHEL/Rocky):
[mysqld]bind-address = 127.0.0.1mysqlx-bind-address = 127.0.0.1mysqlx-bind-address is needed because port 33060 has its own variable: bind-address does not restrict it. These variables cannot be changed on the fly: a restart of the service is required (sudo systemctl restart mysql on Debian/Ubuntu, sudo systemctl restart mysqld on RHEL/Rocky). Then run the ss command again: both lines should now show 127.0.0.1.
SELECT @@bind_address, @@mysqlx_bind_address;Both values should be 127.0.0.1. If a remote client or a replica has to connect, list the required addresses instead, as the Configuration and Security guides explain.
Create a first administration user
Do not work day to day with the root account. Create a dedicated account:
CREATE USER 'labadmin'@'localhost' IDENTIFIED BY 'StrongPassword!2026';GRANT ALL PRIVILEGES ON *.* TO 'labadmin'@'localhost' WITH GRANT OPTION;For an account that must connect from the network (lab, development), the '%' host stands for any address:
CREATE USER 'labadmin'@'%' IDENTIFIED BY 'StrongPassword!2026';GRANT ALL PRIVILEGES ON *.* TO 'labadmin'@'%' WITH GRANT OPTION;Warning: as long as the server listens on all interfaces (see the previous section), this labadmin@'%' account with all privileges is really reachable from the network. Keep it for an isolated lab, restrict the host to your subnet (for example 'labadmin'@'192.168.122.%') or keep only labadmin@'localhost' combined with a local bind-address.
FLUSH PRIVILEGES is unnecessary here. The CREATE USER, GRANT and REVOKE statements take effect immediately. FLUSH PRIVILEGES is only needed if you modify the grant tables (mysql.user, mysql.db) directly.
Put it into practice
The lab hands you db1.lab, a blank Debian 13 machine: no MySQL, no repository for it. You declare Oracle's repository by binding it to the signing key with which Oracle signs its packages (the one apt verifies at every apt update), install MySQL 8.4 LTS and close what the server listens on. The validation reads the actual listening reported by ss, not a configuration file, because two measured facts make the file misleading: Oracle's packages ship no bind-address line (the variable is therefore * by default), and bind-address alone does not restrict port 33060, which has its own variable, mysqlx-bind-address. A server you believe closed is therefore still reachable through its second door.
The validation also checks the expiry date of the key and of its subkeys (secondary keys attached to the main key, each with its own deadline), and that is what made it useful: Oracle's 2023 key expired on 2025-10-22 (its expiry has since been pushed back, see the troubleshooting), and a repository bound to an outdated copy of the key breaks apt-get update without the message mentioning a date.
Troubleshooting
The service does not start
Read the error logs:
# Debian / Ubuntusudo cat /var/log/mysql/error.log | tail -30
# RHEL / Rockysudo cat /var/log/mysqld.log | tail -30
# Or through journalctlsudo journalctl -u mysql --no-pager -n 30 # Debiansudo journalctl -u mysqld --no-pager -n 30 # RHELFrequent causes:
| Symptom | Likely cause | Fix |
|---|---|---|
[ERROR] Can't start server: Bind on TCP/IP port: Address already in use | Another process is using port 3306 | ss -tlnp | grep 3306 to identify the conflict |
[ERROR] --initialize specified but the data directory has files in it | The datadir already contains files | Either delete /var/lib/mysql/*, or use another datadir |
[ERROR] InnoDB: Unable to lock ./ibdata1 | Another mysqld instance is already running | ps aux | grep mysqld and stop the conflicting instance |
| Service not found | Wrong service name | mysql (Debian) vs mysqld (RHEL) |
apt update complains about a signature, although the repository key was imported | The key or one of its subkeys has expired (Oracle's 2023 key expired on 2025-10-22, then its expiry was pushed back to 2027-10-23, with the same fingerprint: a repository or key file left on the old copy still shows it as expired) | gpg --show-keys on the key file: an outdated key is marked [expired: …], you have to fetch the up-to-date key from Oracle |
| A remote client reaches MySQL although you thought access was local only | bind_address is * (default of the Oracle packages) | sudo ss -tlnp | grep mysqld, then bind-address and firewall ("How do you restrict MySQL's network listening?" section) |
ERROR 1819 (HY000): Your password does not satisfy the current policy requirements on an ALTER USER or a CREATE USER | The validate_password component is active, MEDIUM policy by default | Give the password at least 8 characters, a lowercase letter, an uppercase letter, a digit and a special character. Read the policy in force with SHOW VARIABLES LIKE 'validate_password%' |
Lost root password
If you lose the root password, you can reset it in maintenance mode (--skip-grant-tables). The procedure differs between Debian/Ubuntu, where you start mysqld by hand, and RHEL/Rocky, where you pass the options through a systemd environment variable:
-
Stop MySQL
Terminal window sudo systemctl stop mysql -
Start in skip-grant-tables mode
Terminal window sudo mysqld --skip-grant-tables --skip-networking --user=mysql &The
--skip-networkingoption prevents any network connection during this sensitive operation. -
Connect without a password and reset
Terminal window mysql -u rootFLUSH PRIVILEGES;ALTER USER 'root'@'localhost' IDENTIFIED BY 'YourPassword!2026'; -
Stop the process and restart normally
Terminal window sudo kill $(cat /var/run/mysqld/mysqld.pid)sudo systemctl start mysql
-
Stop MySQL
Terminal window sudo systemctl stop mysqld -
Start in skip-grant-tables mode, without network
Terminal window sudo systemctl set-environment MYSQLD_OPTS="--skip-grant-tables --skip-networking"sudo systemctl start mysqldThe
MYSQLD_OPTSvariable is passed tomysqldby the systemd unit. The--skip-networkingoption cuts every network connection during the operation. -
Connect without a password and reset
Terminal window mysql -u rootFLUSH PRIVILEGES;ALTER USER 'root'@'localhost' IDENTIFIED BY 'YourPassword!2026'; -
Remove the options and restart normally
Terminal window sudo systemctl unset-environment MYSQLD_OPTSsudo systemctl restart mysqld
Conflict with MariaDB
If you get a package conflict error at installation:
# List the installed MariaDB packagesdpkg -l | grep mariadb
# Uninstall MariaDB (data is kept unless you purge)sudo apt remove --purge mariadb-server mariadb-client mariadb-common
# Clean the datadir if neededsudo rm -rf /var/lib/mysql/*
# Reinstall MySQLsudo apt updatesudo apt install -y mysql-serverKey points
- Use the Oracle repositories to get MySQL 8.4 LTS, because distribution repositories often ship MariaDB or an old MySQL version.
- On Debian/Ubuntu, the installer offers to set the root password (leaving it empty enables Unix socket authentication). On RHEL/Rocky, a temporary password is generated in
/var/log/mysqld.log. - The service is called
mysqlon Debian andmysqldon RHEL, do not mix them up. - Run
mysql_secure_installationright after installation to remove anonymous accounts and the test database: it never touches the network listening. - An active service is running now, an enabled service will start at the next boot: only
systemctl is-enabledguarantees that the instance comes back after a restart. - An
apt updatethat complains about a signature calls first for agpg --show-keyson the repository key: the key or one of its subkeys may have expired. - The Oracle packages let
mysqldlisten on all interfaces (bind_address = *, ports 3306 and 33060): add a firewall orbind-address = 127.0.0.1(andmysqlx-bind-address) in the configuration. - The default authentication plugin is
caching_sha2_password(SHA-256), more secure than the oldmysql_native_password. - The server configuration lives in
/etc/mysql/mysql.conf.d/mysqld.cnf(Debian) or/etc/my.cnf(RHEL). - The buffer pool at 128 MB by default is insufficient for production, the Configuration guide details the sizing.
- Create a dedicated account for daily work, use
rootonly for system administration.
Knowledge check
Eight questions on what Oracle's packages ship and do not ship, the two doors MySQL opens, the signing key and its subkeys, the difference between an active and an enabled service, and what mysql_secure_installation never touches.
Knowledge check
Check what you have learned with this interactive quiz
How it works
- The timer starts when you click Start
- Multiple choice, true/false and short answer questions
- You can move between questions
- Detailed results are shown at the end
Starts the quiz and the timer
Review
(0/0)Score by domain
A domain groups several guides and gets several questions, so its score is more reliable than a per-topic score, which often rests on a single question.
Summary
Strengths
No topic passed at 80% on at least two questions yet.
Weak areas
No topic below the pass mark.
Recommended next
Hints before you try again?
A new full quiz with random questions
Work again only on the questions you missed