SKDBLOG

DevOps

Enable MySQL Remote Connections on Linux: mysqld.cnf, Port 3306, and Firewall Discipline

MySQL listens on localhost until you say otherwise. When you really need another host on the wire, line up mysqld.cnf bind-address edits, systemd restarts, listener checks, and UFW intent the same way you would for Postgres or Redis on the same VPS.

Shashikant Dwivedi
5 min read
Enable MySQL Remote Connections on Linux: mysqld.cnf, Port 3306, and Firewall Discipline
DevOps05 MIN

If you are standing up a MySQL 8 install on Ubuntu before networking hardens anything, treat this note as the follow-on chapter: defaults keep sockets on 127.0.0.1, which is healthy until a second machine honestly needs native drivers instead of SSH tunnels.

⏱️ Estimated time: about five minutes of edits plus whatever your change window demands for reloads.

Why exposing database ports is never “just firewall later”

Opening TCP 3306 toward the public internet is workable in labs and painful in prod. Prefer VPN paths, bastion SSH tunnels, cloud security groups, or IP allow lists; keep passwords and MySQL grants boringly tight; and assume scanners will find the listener if it is globally reachable.

Prerequisites and risk framing

Before opening nano, line up:

  • MySQL installed and running with sudo access on the box
  • The server IP your clients dial—public, private VPC, or both—written down so grants and firewall rules stay honest
  • A MySQL user that may authenticate remotely ('app'@'%' is easy and lazy; prefer 'app'@'10.0.0.12' when you know the sender)

When I rough in UFW sequencing without retyping muscle memory, I still paste from the Nginx + UFW command slab so ufw status verbose cadence matches every other Ubuntu database article on this site.

Parallel bedtime reading: PostgreSQL remote access follows the same story with listen_addresses and pg_hba.conf instead of bind-address—different files, same paranoia.

Edit bind-address in mysqld.cnf

Open the daemon config Ubuntu commonly ships under mysql.conf.d:

bash
sudo nano /etc/mysql/mysql.conf.d/mysqld.cnf

Find the listener guard:

bash
bind-address            = 127.0.0.1

127.0.0.1 means local-only sockets—exactly what you want until remote access is a deliberate policy call.

To let other interfaces accept handshakes, widen it:

bash
bind-address            = 0.0.0.0

0.0.0.0 is the blunt instrument: every NIC listens. Prefer pairing it with security groups, VPN paths, or tightly scoped UFW rules rather than treating the change as “job done.”

Restart MySQL and verify port 3306

Reload mysqld so the file matters:

bash
sudo systemctl restart mysql

Watch health:

bash
sudo systemctl status mysql

Confirm something is listening where clients expect:

bash
sudo netstat -tuln | grep 3306

On modern images you can mirror the same signal with ss -tuln | grep 3306 if netstat feels legacy. You want to see 0.0.0.0:3306 (or the explicit IP you bound) instead of only 127.0.0.1:3306.

Firewall: UFW and MySQL port 3306

Host firewalls are the last mile after mysqld listens. A typical wide-open lab rule:

bash
sudo ufw allow 3306/tcp

Production-shaped setups usually pass from constraints or lean on cloud security groups so the entire planet never reaches the port just because UFW said “allow.” If you already trust paste-friendly Ubuntu UFW lines on web-tier boxes, keep that snippet beside this tab—the ordering of allow, enable, and status verbose is the habit, not MySQL-specific grammar.

Troubleshooting remote MySQL connections

When drivers still time out after the edits above, walk the boring checklist:

  • Servicesystemctl status mysql should be active; journalctl -u mysql -e --no-pager narrates boot failures faster than guessing.
  • Listenernetstat/ss should prove 3306 binds beyond localhost once policy requires it.
  • PrivilegesSELECT user,host FROM mysql.user; should show a row that matches your client origin; anonymous grants are not “convenience,” they are debt.
  • Edge firewalls — corporate laptops or cloud SGs block outbound SQL more often than people admit.
  • Syntax — a stray character in mysqld.cnf prevents mysqld from applying the bind you think you saved.

For another database with the same “widen bind, restrict path” rhythm, Redis remote access on Ubuntu is a useful cross-check. When you need the ufw allow / reload boilerplate without rereading man pages, that single-page nginx plus UFW snippet is still what I open next to MySQL tabs.


You now know how to flip bind-address, restart mysql, prove 3306 listens, and stack firewall intent so convenience does not steamroll policy—keep the Ubuntu install walkthrough handy when binaries need refresh, and revisit the PostgreSQL listener guide any time two databases share one VPS story.

Frequently asked questions

When is 0.0.0.0 the right bind-address for MySQL?

Reach for 0.0.0.0 when every interface must accept traffic and you already plan to constrain who may route to those interfaces—UFW or cloud rules, VPN-only paths, and MySQL grants scoped to real client networks. When you only need one NIC, binding that address directly often shrinks the accidental blast radius compared with listening everywhere “because it worked in a tutorial.”

Why can clients still not connect after changing bind-address?

Typical culprits are an unsaved config, a skipped systemctl restart mysql, MySQL users that still only allow localhost, another process colliding on 3306, or upstream firewalls (cloud SGs, office egress filters) blocking SQL even though mysqld is perfect locally. Re-read status, ss/netstat, and error logs before assuming the driver string is wrong.

Is opening UFW for port 3306 enough for remote MySQL?

Not by itself. UFW (or any firewall) only shapes packet paths. You still need authentication that matches remote hosts, secrets that survive audits, and—when possible—peers limited to known IPs or tunnels rather than the whole WAN. sudo ufw allow 3306/tcp is a teaching shortcut, not a production seal of approval.

Does MySQL use TCP port 3306 by default for remote clients?

Conventional installs listen on TCP 3306 unless you override port in configuration or front the database with something else entirely. Remote clients therefore aim at hostname:3306 unless your ops team standardized a different listener or jump host path.

Written by Shashikant Dwivedi

Engineer, occasional writer, full-time noticer. Based in Prayagraj, India. New essays land roughly twice a month.

Keep reading

Adjacent essays.

All writing →

The newsletter

New articles in your inbox.

Occasional articles on engineering, tooling, and software development practices. No marketing, no fluff — just the article, when it's ready.

Unsubscribe with one click. Your email never leaves the list.