Chapter 6
Chapter 6 — Securing Remote Access
The fastest way to get your MySQL server compromised is to expose port 3306 to the internet with bind-address = 0.0.0.0 and no firewall. Bots scan for it continuously — if they find it, they'll attempt to brute-force every account within minutes. This chapter covers the right way to think about remote access: restricting what MySQL listens on, layering firewall rules, enforcing SSL, and — for home servers — the much better alternative of an SSH tunnel that opens nothing to the internet at all.
bind-address options and which to use. UFW rules scoped to specific IPs. SSL/TLS status in MySQL 8.0 (on by default), verifying encryption is in use, and enforcing it per-user. The SSH tunnel — why it's the right choice for home servers wanting GUI tool access, with a complete walkthrough. Three practical scenarios covering local-only, LAN access, and remote access. Troubleshooting.
First Question: Do You Actually Need Remote Access?
Before configuring anything, decide whether remote access to MySQL is genuinely needed. Most web servers run the application and the database on the same machine — PHP connects to MySQL via the Unix socket, and bind-address = 127.0.0.1 is the correct and secure configuration. Remote access is only needed when:
- Your application runs on a separate server — a dedicated app tier connecting to a dedicated database server
- You want to use a GUI tool like MySQL Workbench from your development machine
- You're running replication — a replica server reading from the primary
- A backup script on a different machine needs to dump the database
If none of these apply, leave bind-address = 127.0.0.1 and skip the rest of this chapter — you're already in the most secure configuration possible.
Checking What MySQL Is Currently Listening On
The Three-Layer Security Model
Remote MySQL access is only possible when all three layers permit it. Each layer is independently controlled. Missing any one of them blocks the connection.
127.0.0.1 = local only, 0.0.0.0 = all interfaces (internet-accessible). This is the first gate — if MySQL isn't listening on an interface, no connection from that network is even possible.ufw allow 3306 without a source restriction.@host matches the source IP. 'user'@'192.168.1.50' will only authenticate from that exact IP — all others get access denied.Configuring bind-address
Change it in /etc/mysql/mysql.conf.d/mysqld.cnf:
ip addr show | grep 'inet '. Use the address on your LAN interface (typically eth0, ens3, or similar — not lo). If that IP changes (DHCP), MySQL won't start next time. Set a static LAN IP on your server or router before using this approach.
UFW Rules for MySQL
ufw allow 3306 without a source restriction. This opens MySQL to the entire internet. Bots scan for port 3306 continuously. Always scope the rule to specific IPs or subnets.
SSL/TLS — Encrypting the Connection
Even on a private LAN, credentials and query results travel in plain text if the connection isn't encrypted. MySQL 8.0 solves this by generating self-signed SSL certificates automatically at first startup and enabling SSL by default — no configuration required. This section shows how to verify encryption is working and how to enforce it.
Checking SSL status
Ssl_cipher will be empty. This is fine — socket connections never leave the machine.
Ssl_cipher empty on a TCP connection from another machine = credentials and data are in plain text on the wire. Add --ssl-mode=REQUIRED to enforce SSL for remote clients.
Connecting with SSL from a remote client
Enforcing SSL for specific users
PDO::MYSQL_ATTR_SSL_CA option or by setting ssl_mode=REQUIRED in the DSN. Without this, even if MySQL requires SSL for the account, PHP may send a plain-text connection and get rejected.
The SSH Tunnel — The Right Answer for Home Servers
Opening port 3306 to the internet, even with UFW restrictions and SSL, adds a persistent attack surface. If your MySQL is only accessed remotely by you — from your dev machine, for administration or using MySQL Workbench — an SSH tunnel gives you full access with no port opened, no firewall rules for MySQL, and no credentials exposed. The tunnel forwards a local port on your machine through your SSH connection to MySQL on the server.
bind-address stays at 127.0.0.1 — no changes needed to MySQL config.
Three Practical Scenarios
127.0.0.1 (default).UFW: no rule for port 3306 — not needed.
User accounts:
'app_user'@'localhost' — already correct.SSL: not needed for socket connections.
This is the most secure configuration. PHP connects via Unix socket (
host=localhost in DSN). Nothing listens on a network interface for MySQL. Zero attack surface.
192.168.1.100). This only exposes MySQL on the LAN interface — not to the internet even without UFW.UFW:
sudo ufw allow from 192.168.1.0/24 to any port 3306 comment 'MySQL LAN'User account:
CREATE USER 'dev_user'@'192.168.1.%' IDENTIFIED BY '...' REQUIRE SSL;SSL: enforce with
REQUIRE SSL on the account and --ssl-mode=REQUIRED on the client.Only reachable from inside your LAN. If your server is behind a home router, it's not directly internet-accessible anyway — but binding to the LAN IP and using UFW provides defence in depth.
127.0.0.1 (no changes).UFW: no rule for port 3306 — not needed.
User accounts:
'admin'@'127.0.0.1' — the tunnel makes remote connections appear local.SSL: not needed — the SSH channel is already encrypted.
To connect:
ssh -L 3307:127.0.0.1:3306 philip@server.example.com -N, then connect MySQL to 127.0.0.1:3307. MySQL Workbench supports this natively via "Standard TCP/IP over SSH".Zero attack surface for MySQL. The only thing exposed is SSH (port 22), which is already protected by key-based auth and SSH hardening from the Security course.
Troubleshooting
sudo ss -tlnp | grep 3306 — does it show 0.0.0.0:3306? If not, restart MySQL after saving the config change. Layer 2: sudo ufw status — is there a rule allowing the source IP on port 3306? Layer 3: SELECT user, host FROM mysql.user WHERE user='youruser'; — does the host column match the connecting IP (exactly or via wildcard)? All three must pass.
bind-address doesn't exist on any network interface on the server. Check available IPs: ip addr show. If you set a static LAN IP that hasn't been assigned yet, or your DHCP IP changed, MySQL can't bind to it. Either fix the network config to assign that IP statically, or change bind-address back to 127.0.0.1. Check the error log: sudo tail -20 /var/log/mysql/error.log.
127.0.0.1 (loopback), not from your actual dev machine IP. The MySQL user account must have @'localhost' or @'127.0.0.1' as its host — not @'192.168.1.50'. Check: SELECT user, host FROM mysql.user WHERE user='youruser';. If necessary: CREATE USER 'youruser'@'127.0.0.1' IDENTIFIED BY 'pass'; and grant the same privileges.
SHOW VARIABLES LIKE 'tls_version';. Also check the client side: for PHP, ensure OpenSSL is recent. A quick diagnostic: mysql -u user -p -h host --ssl-mode=REQUIRED --tls-version=TLSv1.2 — if this works, it's a TLS 1.3 compatibility issue on the client.
sudo ufw status — should say "Status: active". Also verify the rule direction: ufw allow from creates an ALLOW IN rule (for incoming connections), which is what you want. Check the rule is listed: sudo ufw status numbered | grep 3306. If the server is behind a NAT router (e.g. at home), the connecting machine's IP visible to the server is the LAN IP, not the internet IP — make sure your UFW rule matches the LAN IP.
Quick Reference — Chapter 6
| Command / Setting | Purpose |
|---|---|
| sudo ss -tlnp | grep mysql | Check which interfaces and ports MySQL is listening on |
| bind-address = 127.0.0.1 | Listen on localhost only — safest for single-server setups (in mysqld.cnf) |
| bind-address = 0.0.0.0 | Listen on all interfaces — only use if remote access is needed, must combine with UFW |
| sudo ufw allow from 192.168.1.50 to any port 3306 | Open MySQL to a specific IP only — always scope to source IP, never bare "allow 3306" |
| SHOW STATUS LIKE 'Ssl_cipher'; | Check if the current connection is SSL encrypted (empty = not encrypted) |
| SHOW VARIABLES LIKE 'have_ssl'; | Confirm SSL is available on the server (YES = auto-generated certs in /var/lib/mysql) |
| ALTER USER 'u'@'h' REQUIRE SSL; | Force SSL for a specific user — connections without SSL are rejected |
| mysql --ssl-mode=REQUIRED -h host -u user -p | Connect with SSL enforced (client-side flag) |
| ssh -L 3307:127.0.0.1:3306 user@server -N | Open SSH tunnel: connect locally on port 3307 to reach remote MySQL on 3306 |
| mysql -h 127.0.0.1 -P 3307 -u user -p | Connect to MySQL through the SSH tunnel (run after the tunnel is open) |
| SHOW CREATE USER 'u'@'h'\G | View a user's SSL requirements and other account settings |