Short answer: MySQL and MariaDB listen on TCP port 3306 by default. MySQL 8 also opens 33060 for the X Protocol used by MySQL Shell. Check the real port with SHOW VARIABLES LIKE 'port'; or sudo ss -tlnp | grep 3306, connect to another port with a capital -P, and keep 3306 closed to the internet.
| Port | Product | Used for | Reachable from |
|---|---|---|---|
| 3306/tcp | MySQL, MariaDB | The classic client protocol: applications, the mysql client, mysqladmin, phpMyAdmin, dumps | The server itself, or one app server |
| 33060/tcp | MySQL 5.7.12 and newer, on by default since 8.0 | X Protocol: MySQL Shell, X DevAPI, Document Store | The server itself |
| 33062/tcp | MySQL 8.0.14 and newer | A separate admin connection, only when admin_address is set | The server itself |
| 33061/tcp | MySQL Group Replication | Traffic between group members (the port the manual uses) | Other members only |
| 6446, 6447 | MySQL Router | Read-write and read-only routing in an InnoDB Cluster | App servers only |
| 4567, 4568, 4444 | MariaDB Galera Cluster | Replication, incremental and full state transfer | Other nodes only |
| Unix socket | Both | Connections to localhost: /run/mysqld/mysqld.sock on Debian and Ubuntu, /var/lib/mysql/mysql.sock on Rocky | Local processes, no port |
MariaDB uses the same port 3306 and has no X Protocol, so on Debian, where the default MySQL packages install MariaDB, you only ever see 3306. Port 3306 is registered with IANA as mysql, which is why ss without -n prints :mysql instead of the number.
How to check which port MySQL is using
Ask the server first. It reports the port it actually opened, whatever the config files say:
SHOW VARIABLES WHERE Variable_name IN ('port', 'bind_address', 'skip_networking');+-----------------+-----------+
| Variable_name | Value |
+-----------------+-----------+
| bind_address | 127.0.0.1 |
| port | 3306 |
| skip_networking | OFF |
+-----------------+-----------+
3 rows in set (0.001 sec)- port is the TCP port.
SELECT @@port;returns the same number on its own. - bind_address says on which addresses:
127.0.0.1means only programs on the server can connect over TCP;0.0.0.0or*means every address. - skip_networking ON means no TCP at all: the server answers only on its Unix socket, whatever
portsays.
Then look at the socket table, which also shows the X Protocol port and the process:
sudo ss -tlnp | grep -E ':3306|:33060'LISTEN 0 80 127.0.0.1:3306 0.0.0.0:* users:(("mariadbd",pid=811,fd=21))LISTEN 0 151 127.0.0.1:3306 0.0.0.0:* users:(("mysqld",pid=1042,fd=23))
LISTEN 0 70 127.0.0.1:33060 0.0.0.0:* users:(("mysqld",pid=1042,fd=21))127.0.0.1:3306 is the safe default the Debian and Ubuntu packages ship. Rocky Linux sets no bind-address, so there you see *:3306 and only firewalld keeps the port closed. What the other address forms mean is in checking open ports on Linux. To find which file sets the values:
sudo grep -rnE '^\s*(port|bind-address|bind_address|skip-networking)' /etc/mysql/ /etc/my.cnf /etc/my.cnf.d/ 2>/dev/nullmysqladmin: the port from the command line
# the port and bind address of the running server
sudo mysqladmin variables | grep -wE 'port|bind_address'
# is a server answering on this host and port?
mysqladmin -h 127.0.0.1 -P 3306 -u app -p ping| bind_address | 127.0.0.1 |
| port | 3306 |
mysqld is alivegrep -w matches port as a whole word, so extra_port and mysqlx_port stay out of the result. mysqladmin ping with an explicit host and port is a handy health check for scripts: it exits with 0 when the server answers, even if the login itself is refused. On MariaDB the same tool is also called mariadb-admin, and newer MariaDB releases warn that the mysql-prefixed names are deprecated.
-P or -p: the capital letter matters
| Option | Meaning | Example |
|---|---|---|
-P 3307 | TCP port (long form --port=3307) | mysql -h 127.0.0.1 -P 3307 -u app -p |
-p | Ask for the password (long form --password) | mysql -u app -p |
-pSECRET | Password inline, no space. Lands in your shell history, and on some systems in ps | Avoid; use ~/.my.cnf |
-h localhost | Connect through the Unix socket, not TCP | mysql -h localhost -u app -p |
-h 127.0.0.1 | Connect over TCP to the local server | mysql -h 127.0.0.1 -P 3306 -u app -p |
--protocol=TCP | Force TCP whatever the host name | mysql --protocol=TCP -P 3307 -u app -p |
-S PATH | Use this socket file | mysql -S /run/mysqld/mysqld.sock -u app -p |
Two mistakes come up again and again:
$ mysql -u app -p 3307
Enter password:
ERROR 1049 (42000): Unknown database '3307'Lowercase -p takes the password only when it is glued to the option (-pSECRET), so the separate 3307 is read as the name of a database. The second trap is localhost. The MySQL manual says that on Unix the mysql client treats localhost as “use the socket file”, even when -P names a port, so mysql -h localhost -P 3307 quietly reaches whichever server owns the default socket. MariaDB’s client differs: its man page (10.11, as shipped in Debian 12) says that --port on the command line forces TCP only “without other connection properties”, so a host name next to it can still mean the socket. To be sure with either client, write -h 127.0.0.1 or add --protocol=TCP.
How to change the MySQL port
- 1Set the port in the server configuration
under [mysqld], in the file that holds bind-address # MariaDB on Debian/Ubuntu: /etc/mysql/mariadb.conf.d/50-server.cnf # MySQL on Ubuntu: /etc/mysql/mysql.conf.d/mysqld.cnf # Rocky: a file in /etc/my.cnf.d/ [mysqld] port = 3307 - 2Rocky and AlmaLinux: tell SELinux about the new port
Rocky · AlmaLinux sudo semanage port -a -t mysqld_port_t -p tcp 3307Without it, SELinux stops the server from binding the port and it fails to start.
semanagecomes withpolicycoreutils-python-utils. - 3Restart and check
Debian · Ubuntu · Rocky sudo systemctl restart mariadb # or: mysql (Ubuntu), mysqld (Rocky) sudo ss -tlnp | grep 3307 - 4Update every client that connects over TCP
~/.my.cnf (mode 600) [client] port = 3307Applications that connect to
127.0.0.1need the new port in their settings, for exampledefine( 'DB_HOST', '127.0.0.1:3307' );in WordPress’swp-config.php. Anything that useslocalhostthrough the socket (PHP’s default forlocalhost,sudo mariadb) keeps working unchanged, because a socket has no port.
If the port is open to a remote app server, move the firewall rule to the new number too. Changing the port is housekeeping, not security: scanners try every port, and what protects a database is the bind address and the firewall, as with changing the SSH port.
Remote access to port 3306, done safely
| Who connects | Port 3306 open? | How |
|---|---|---|
| An app on the same server | No | localhost (socket) or 127.0.0.1 |
| You, with a GUI tool or the mysql client | No | An SSH tunnel to 127.0.0.1:3306 |
| An app server in a private network or VPN | Only on that interface | bind-address set to the private or WireGuard address |
| An app server across the internet | Only for that one IP | bind-address 0.0.0.0, a firewall rule for the IP, a user for that host, TLS |
The tunnel is the right answer far more often than people expect. It needs nothing on the server beyond SSH:
ssh -N -L 3307:127.0.0.1:3306 you@203.0.113.10
# then, in a second terminal or in your GUI tool:
mysql -h 127.0.0.1 -P 3307 -u app -pLocal port 3307 avoids a clash with a database on your own computer. SSH tunnels covers keeping one open, and the built-in tunnel option of DBeaver, HeidiSQL and MySQL Workbench does the same thing. When an app server elsewhere truly needs a direct connection, open the port to its address only:
# firewall: one source address, TCP 3306
sudo ufw allow from 198.51.100.7 to any port 3306 proto tcp
# in sudo mariadb / sudo mysql: a user for that host only, TLS required
CREATE USER 'app'@'198.51.100.7' IDENTIFIED BY 'a-long-random-password' REQUIRE SSL;
GRANT ALL PRIVILEGES ON app.* TO 'app'@'198.51.100.7';REQUIRE SSL works once the server has a certificate: MySQL 8 creates one by itself, MariaDB 10.11 needs ssl_cert and ssl_key set. The full walkthrough, including bind-address and firewalld, is in installing MariaDB or MySQL.
Port 33060: the X Protocol
MySQL’s X Plugin speaks a second protocol on 33060, used by MySQL Shell, the X DevAPI connectors and the Document Store (JSON collections). Ordinary applications, PHP, WordPress and phpMyAdmin use 3306 only.
SHOW VARIABLES LIKE 'mysqlx%port';
SHOW VARIABLES LIKE 'mysqlx_bind_address';Ubuntu’s mysqld.cnf binds it to 127.0.0.1 with mysqlx-bind-address, next to bind-address. If nothing uses it, switch the plugin off with mysqlx = OFF under [mysqld] and restart. MySQL Shell then connects over the classic protocol with mysqlsh mysql://app@127.0.0.1:3306.
Connection errors and what the numbers mean
The number in brackets at the end of a Can't connect error is the operating system’s error code, and it tells you which side to fix. MariaDB’s client words it as Can't connect to server on '203.0.113.10' (111), with the same numbers.
| Error | Meaning | Fix |
|---|---|---|
ERROR 2003 (HY000): Can't connect to MySQL server on '203.0.113.10:3306' (111) | Connection refused: the host answered, but nothing listens on that address and port | bind-address still 127.0.0.1, skip_networking on, wrong port, or the server is down |
... (110) | Timed out: packets are dropped on the way (MariaDB's client may show 115 here instead) | A firewall (on the server or the provider's), or the wrong IP |
... (113) | No route to host | Usually firewalld rejecting the port; add the rich rule |
... (10061) or (10060) | The Windows codes for refused and timed out | As for 111 and 110; on a Windows server also check that the MySQL service runs |
ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2) | localhost means the socket, and there is no socket: the server is not running, or uses another path | systemctl status mariadb (or mysql); or connect with -h 127.0.0.1 |
ERROR 1130 (HY000): Host '198.51.100.7' is not allowed to connect to this MySQL server | The port is fine; no account exists for that client address | CREATE USER for that host (MariaDB says MariaDB server) |
ERROR 1045 (28000): Access denied for user | The port is fine; the user, password or authentication plugin is not | See the access denied guide below |
ERROR 2013 (HY000): Lost connection to MySQL server at 'reading initial communication packet' | TCP connected, but no MySQL greeting came back | Something else answers on that port, or a tunnel points to a closed port |
For Access denied for user 'root'@'localhost' in particular, the cause is almost never the port; read MySQL access denied for root. To test the port alone from the app server, without credentials:
nc -vz -w 5 203.0.113.10 3306 # Linux, macOS
Test-NetConnection 203.0.113.10 -Port 3306 # Windows PowerShellWhere the database should run
On a single VPS that runs both the app and the database, the database never needs a public port: keep bind-address = 127.0.0.1, let the app use localhost, and do admin work through the tunnel. Once you split them across two servers, keep both in the same location: VMHeaven runs VPS in Amsterdam and Frankfurt, and an app in one city querying a database in the other pays that round trip on every query.
Frequently asked
What is the default MySQL port?
TCP port 3306, for both MySQL and MariaDB. MySQL 8.0 and newer also listen on 33060 for the X Protocol used by MySQL Shell (MySQL 5.7.12 to 5.7 offered it only as a plugin you installed). Connections to localhost usually go through the Unix socket and use no port at all.
How do I find which port MySQL is running on?
Run SHOW VARIABLES LIKE 'port'; in the mysql or mariadb client, or sudo mysqladmin variables | grep -w port in the shell. sudo ss -tlnp | grep 3306 shows whether the server listens on that port, on which address and under which process.
How do I connect to MySQL on a different port?
Use a capital -P together with a TCP host: mysql -h 127.0.0.1 -P 3307 -u app -p. Lowercase -p asks for the password. With -h localhost, Oracle's mysql client uses the Unix socket and ignores -P, so write 127.0.0.1 or add --protocol=TCP.
Is it safe to open port 3306 to the internet?
No. Keep MySQL on 127.0.0.1 and connect remotely through an SSH tunnel. If an application server elsewhere must connect directly, allow port 3306 in the firewall only from that server's IP address, create the database user for that host only, and require TLS.