Why Should Web and Database Communication Not Traverse the Public Internet?
Imagine your web server runs on a public IP and your MySQL database runs on another server with a public IP. To connect, the application connects to an address like 203.0.113.25:3306. This means every packet exchanged between these two servers traverses the internet and is exposed to eavesdropping, tampering, or brute-force attacks. Even with a strong password, port 3306 on a public IP is constantly attacked by scanners.
The standard solution is to use a private network. In this architecture, each server, in addition to its public IP, receives a private IP (such as 10.10.0.5) that is only accessible from within the data center network. Traffic between these IPs never goes to the public internet; instead, it passes through the data center's internal switches and routers. The result: lower latency (typically under 0.5 milliseconds), higher security, and lower bandwidth costs.
In this article, you will learn how to set up a private network between a web server and a database, configure the firewall correctly, and identify the most common mistakes. This guide applies to any data center that supports VLAN or VPC definitions (including ServerNet cloud services).
Basic Concepts: Private IP, VLAN, and VPC
Before we start, let's clarify three terms:
- Private IP: An address from the ranges
10.0.0.0/8,172.16.0.0/12, or192.168.0.0/16that is not routable on the internet. - VLAN: Logical network isolation at Layer 2. Two servers in the same VLAN can communicate directly; servers in other VLANs cannot.
- VPC (Virtual Private Cloud): In cloud environments, an isolated network where you can define subnets, routing tables, and gateways yourself.
In both cases, the principle is the same: servers have a second network interface (such as eth1) that is only connected to the internal network. The IP of this interface is invisible from the outside.
Understanding the Target Topology
Our proposed architecture looks like this:
Web Server (Nginx + PHP-FPM)
eth0: 203.0.113.10 ← Public IP
eth1: 10.10.0.10 ← Private IP
Database Server (MySQL 8)
eth0: 203.0.113.20 ← Public IP
eth1: 10.10.0.20 ← Private IP
The application on the web server connects to 10.10.0.20:3306 instead of 203.0.113.20:3306. Port 3306 on the database's public IP remains closed.
Step 1: Enabling the Private Network on Servers
In the data center or cloud management panel, add a second network interface to each server. In a cloud environment, this is usually done with "Attach secondary network." In a physical data center, you need to ask the network team to define a dedicated VLAN for you.
After adding the interface, you need to configure it in Linux. On Debian/Ubuntu-based distributions, edit the file /etc/netplan/01-netcfg.yaml:
network:
version: 2
ethernets:
eth0:
dhcp4: true
eth1:
dhcp4: false
addresses:
- 10.10.0.10/24
routes:
- to: 10.10.0.0/24
via: 10.10.0.1
Then apply the configuration:
sudo netplan apply
ip addr show eth1
The output should include inet 10.10.0.10/24. Repeat the same on the database server with IP 10.10.0.20.
Common Mistake: Forgetting to define a route for the private subnet. If the route is missing, pinging 10.10.0.20 will fail with a "Network is unreachable" error. Always check with ip route after netplan apply to ensure the 10.10.0.0/24 route exists.
Step 2: Configuring the Firewall on the Database Server
Now we need to ensure that MySQL only listens on the private IP. Two layers of protection are required: setting the bind-address in MySQL and firewall rules.
2.1 Setting the bind-address in MySQL
Edit the MySQL configuration file (/etc/mysql/mysql.conf.d/mysqld.cnf on Ubuntu):
[mysqld]
bind-address = 10.10.0.20
port = 3306
This setting makes MySQL listen only on the private IP and ignore incoming requests to the public IP. Then restart the service:
sudo systemctl restart mysql
ss -tlnp | grep 3306
The output should only show 10.10.0.20:3306, not 0.0.0.0:3306.
2.2 UFW Rules
If you are using UFW, first keep the SSH port open so you don't lock yourself out of the server:
sudo ufw allow OpenSSH
sudo ufw allow from 10.10.0.0/24 to any port 3306
sudo ufw enable
The second command only allows servers within the private network to connect to MySQL. Requests from anywhere else (including the internet) are rejected.
Common Mistake: Leaving port 3306 open on the public IP in the firewall. Even if you have set the bind-address, having the port open in the firewall is an additional layer of risk. Always add a deny rule for port 3306 on the internet:
sudo ufw deny 3306/tcp
Step 3: Connecting the Application to the Database via the Private IP
Now you need to connect the application to the new address. For example, in a Laravel .env file:
DB_HOST=10.10.0.20
DB_PORT=3306
DB_DATABASE=app_db
DB_USERNAME=app_user
DB_PASSWORD=a_strong_long_password
In WordPress, the wp-config.php file:
define('DB_HOST', '10.10.0.20:3306');
After the change, clear the application cache and test the connection:
mysql -h 10.10.0.20 -u app_user -p -e "SELECT 1;"
If you get a Connection refused error, check the following in order:
- Is
eth1up on both servers? (ip link show eth1) - Is there a route to the private subnet? (
ip route) - Is MySQL listening on the private IP? (
ss -tlnp | grep 3306) - Does UFW have an allow rule for the subnet? (
sudo ufw status verbose)
Step 4: Final Testing and Monitoring
To ensure that traffic does not traverse the internet, you can use the traceroute tool:
traceroute -n 10.10.0.20
The output should only have one or two hops (e.g., the internal gateway and the server itself). If you see many hops, it means traffic is going through the wrong path.
Also, test port 3306 on the public IP from outside:
nc -zv 203.0.113.20 3306
This command should fail with a Connection refused or timed out error. If it succeeds, the firewall or bind-address is not configured correctly.
Security and Performance Benefits of a Private Network
With this architecture, you gain several important benefits:
- Eliminating the attack surface: The database port is hidden from the internet. Automated scanners will no longer find it.
- Reduced latency: Traffic passes through the data center's internal path rather than the internet, which may have several extra hops.
- Bandwidth savings: Internal traffic is usually free or much cheaper than internet traffic.
- Simplified management: No need to rotate the database IP or use complex VPNs.
Migration Timeline and Practical Considerations
If your database is currently running on a public IP, perform the migration during a maintenance window:
- Change the bind-address setting and restart MySQL.
- Change the application configuration file to the private IP.
- Test the connection and then add the deny rule in the firewall.
- Test port 3306 on the public IP with an external tool.
If you have multiple applications connecting to the database, change them all at the same time to avoid unintended downtime.
Summary
Using a private network between the web server and the database is a simple yet critical step for infrastructure security. With a few minutes of configuration, you isolate sensitive database traffic from the public internet, drastically reduce the attack surface, and improve latency. This pattern can be implemented in any data center and is recommended for any application that uses a database — from WordPress to custom applications.
If you work in a cloud environment, ServerNet services allow you to define a Virtual Private Cloud (VPC) that implements exactly this pattern with a few clicks. But even in a physical environment, you can have your own dedicated VLAN by coordinating with the network team.
Finally, remember that security is not a single layer. The private network is the first step; however, you should still take strong passwords, two-factor authentication for server access, and regular log monitoring seriously.