Deploying discrete components in your application setup onto different nodes is a common way to decrease load and begin scaling horizontally. A typical example is configuring a database on a separate server from your application. While there are a number of advantages with this setup, connecting over a network involves a new set of security concerns.
In this guide, we’ll demonstrate how to set up a firewall on each of your servers in a distributed setup. We will configure our policy to allow the intended traffic between our components while denying other traffic.
You can also configure DigitalOcean’s Cloud Firewalls which run as an additional, external layer to your servers on DigitalOcean infrastructure. This way, you do not have to configure a firewall on your servers themselves.
For the demonstration in this guide, we’ll be using two Ubuntu 22.04 servers. One will have a web application served with Nginx and the other will host the MySQL database for the application. Although we will be using this setup as an example, you should be able to extrapolate the techniques involved to fit your own server requirements.
To get started, you will have to have two fresh Ubuntu 22.04 servers. Add a regular user account with sudo
privileges on each. To do this, follow our Ubuntu 22.04 initial server setup guide.
The application setup we will be securing is based on this guide. If you’d like to follow along with that example, set up your application and database servers as indicated by that tutorial. Otherwise, you can use this article as a general reference.
You’ll begin by implementing a baseline firewall configuration for each of your servers. The policy that we will be implementing takes a security-first approach. We will be locking down almost everything other than SSH traffic and then poking holes in the firewall for our specific application.
This guide follows iptables
syntax. iptables
is automatically installed on Ubuntu 22.04 using an nftables
backend, so you should not have to install any additional packages.
Using nano
or your favorite text editor, open the /etc/iptables/rules.v4
file:
- sudo nano /etc/iptables/rules.v4
Paste the configuration from the firewall template guide:
*filter
# Allow all outgoing, but drop incoming and forwarding packets by default
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [0:0]
# Custom per-protocol chains
:UDP - [0:0]
:TCP - [0:0]
:ICMP - [0:0]
# Acceptable UDP traffic
# Acceptable TCP traffic
-A TCP -p tcp --dport 22 -j ACCEPT
# Acceptable ICMP traffic
# Boilerplate acceptance policy
-A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
-A INPUT -i lo -j ACCEPT
# Drop invalid packets
-A INPUT -m conntrack --ctstate INVALID -j DROP
# Pass traffic to protocol-specific chains
## Only allow new connections (established and related should already be handled)
## For TCP, additionally only allow new SYN packets since that is the only valid
## method for establishing a new TCP connection
-A INPUT -p udp -m conntrack --ctstate NEW -j UDP
-A INPUT -p tcp --syn -m conntrack --ctstate NEW -j TCP
-A INPUT -p icmp -m conntrack --ctstate NEW -j ICMP
# Reject anything that's fallen through to this point
## Try to be protocol-specific w/ rejection message
-A INPUT -p udp -j REJECT --reject-with icmp-port-unreachable
-A INPUT -p tcp -j REJECT --reject-with tcp-reset
-A INPUT -j REJECT --reject-with icmp-proto-unreachable
# Commit the changes
COMMIT
*raw
:PREROUTING ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
COMMIT
*nat
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
COMMIT
*security
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
COMMIT
*mangle
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
COMMIT
Save and close the file. If you are using nano
, press Ctrl+X
to quit, then when prompted, Y
and then Enter.
If you are implementing this in a live environment, do not reload your firewall rules yet. Loading the rule set outlined here will immediately drop the connection between your application and database server. You will need to adjust the rules to reflect our operational needs before reloading.
To allow communication between your components, you need to know the network ports being used. You could find the correct network ports by examining your configuration files, but an application-agnostic method of finding the correct ports is to just check which services are listening for connections on each of our machines.
You can use the netstat
tool to find this out. Since your application is only communicating over IPv4, we will add the -4
argument but you can remove that if you are using IPv6 as well. The other arguments you need to find your running services are -p
, -l
, -u
, -n
, and -t
, which you can provide as -plunt
.
These arguments can be broken down as follows:
p
: Show the PID and name of the program to which each socket belongs.l
: Show only listening sockets.u
: Show UDP traffic.n
: Show numeric output instead of service names.t
: Show TCP traffic.- sudo netstat -4plunt
On your web server, your output might look like this:
OutputActive Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 1058/sshd
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 4187/nginx
The first highlighted column shows the IP address and port that the service highlighted towards the end of the line is listening on. The special 0.0.0.0
address means that the service in question is listening on all available addresses.
On your database server, your output might look like this:
- sudo netstat -4plunt
OutputActive Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 1097/sshd
tcp 0 0 192.0.2.30:3306 0.0.0.0:* LISTEN 3112/mysqld
You can read these columns exactly the same. In this example, the 192.0.2.30
address represents the database server’s private IP address. In the prerequisite tutorial, you restricted MySQL to the private interface for security reasons.
Take note of the values you find in this step. These are the networking details that you’ll need to adjust your firewall configuration.
On your web server, you need to ensure that the following ports are accessible:
Your database server would have to ensure that the following ports are accessible:
192.0.2.30
(or the interface associated with it)Now that you have the port information you need, you will adjust your web server’s firewall rule set. Open the rules file in your editor with sudo
privileges:
- sudo nano /etc/iptables/rules.v4
On the web server, you need to add port 80 to your list of acceptable traffic. Since the server is listening on all available addresses — web servers generally expect to be accessible from anywhere — you will not restrict the rule by interface or destination address.
Your web visitors will be using the TCP protocol to connect. Your framework already has a custom chain called TCP
for TCP application exceptions. You can add port 80 to that chain, right below the exception for your SSH port:
*filter
. . .
# Acceptable TCP traffic
-A TCP -p tcp --dport 22 -j ACCEPT
-A TCP -p tcp --dport 80 -j ACCEPT
. . .
Your web server will initiate the connection with your database server. Your outgoing traffic is not restricted in your firewall and incoming traffic associated with established connections is permitted, so we do not have to open any additional ports on this server to allow this connection.
Save and close the file when you are finished. Your web server now has a firewall policy that will allow all legitimate traffic while blocking everything else.
Test your rules file for syntax errors:
- sudo iptables-restore -t < /etc/iptables/rules.v4
If no syntax errors are displayed, reload the firewall to implement the new rule set:
- sudo service iptables-persistent reload
On your database server, you need to allow access to port 3306
on your server’s private IP address. In this case, that address was 192.0.2.30
.You can limit access destined for this address specifically, or you can limit access by matching against the interface that is assigned that address.
To find the network interface associated with that address, run ip -4 addr show scope global
:
- ip -4 addr show scope global
Output2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
inet 203.0.113.5/24 brd 104.236.113.255 scope global eth0
valid_lft forever preferred_lft forever
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
inet 192.0.2.30/24 brd 192.0.2.255 scope global eth1
valid_lft forever preferred_lft forever
The highlighted areas show that the eth1
interface is associated with that address.
Next, you will adjust the firewall rules on the database server. Open the rules file with sudo
privileges on your database server:
- sudo nano /etc/iptables/rules.v4
Again, you will be adding a rule to our TCP
chain to form an exception for the connection between your web and database servers.
To restrict access based on the actual address in question, you would add the rule like this:
*filter
. . .
# Acceptable TCP traffic
-A TCP -p tcp --dport 22 -j ACCEPT
-A TCP -p tcp --dport 3306 -d 192.0.2.30 -j ACCEPT
. . .
If you would rather allow the exception based on the interface that houses that address, you can add a rule similar to this one instead:
*filter
. . .
# Acceptable TCP traffic
-A TCP -p tcp --dport 22 -j ACCEPT
-A TCP -p tcp --dport 3306 -i eth1 -j ACCEPT
. . .
Save and close the file when you are finished.
Check for syntax errors with this command:
- sudo iptables-restore -t < /etc/iptables/rules.v4
When you are ready, reload the firewall rules:
- sudo service iptables-persistent reload
Both of your servers should now be protected without restricting the necessary flow of data between them.
Implementing a proper firewall should always be part of your deployment plan when setting up an application. Although we demonstrated this configuration using two servers running Nginx and MySQL, the techniques demonstrated above are applicable regardless of your specific technology choices.
To learn more about firewalls and iptables
specifically, take a look at the following guides:
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!
Hello, within this setup, how will one open port 443 on the web server? Will it simply be as follows in /etc/iptables/rules.v4?
If so, these already exist in my settings. Yet
nmap -p443 mysite.com
shows me port 443 is filtered. How do I open it?