How to set up a firewall properly on your web server

Or butterwall. Nobody wants to touch a butter wall.

One of the things that get usually ignored on a server is the firewall. A firewall is a security measure to block all unwanted connections. Unfortunately, the rules are written in gibberish. That's how I felt every time I had set up a new server. I have to search through multiple tutorials to find the right values to add to my settings. This is my attempt to document as much as possible so I can come back here to refresh my mind, and you can help yourself too of course.

When you install a new server, all incoming and outgoing connections flow like a well oiled machine. We, as the masters of the computer, don't want that. At least not on a server that will be used to host our applications. We want to block all connections except those we manually allow.

For example, we connect to our web server through SSH, so it is a good idea to let that application communicate freely on this port. For a web server, we need to let the HTTP port be open for communication as well. If for some reason, we want to connect to our database remotely, we would want to give it access as well. And last, we can allow ftp, so we can upload our files to our server.

So in this post, we will learn how to allow those connections to be established:

Before we get started, let's figure out how the firewall works on a Linux machine, Ubuntu to be specific.

How firewall works.

The Ubuntu firewall is called iptables. Like we stated earlier, by default it allows all connections without exceptions. To see the current rules in place this is the command:

sudo iptables -L

And we get:

Chain INPUT (policy ACCEPT)
target     prot opt source               destination

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination

Each of the chains is currently empty. Their names are self explanatory. Input is for incoming connections, output is for outgoing, and forward allows packets to be rerouted.

The way we can setup rules is by appending rules to each sections. We append by using the -A directive:

sudo iptables -A INPUT -p tcp --dport 80 -j ACCEPT

What this line does is, append a rule to the incoming traffic (-A INPUT) on the TCP protocol (-p tcp), if the port number is 80 (--dport 80) accept the connection (-j ACCEPT).

If we want to allow connections that are already established, we can use the -m conntrack in conjunction with --ctstate. What this does is allow us to filter a connection based on its current state.

sudo iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

Here we are appending a new rule that checks the current state of the connection and only accept it if the connection is already established or related to a previously accepted one.

The order in which the rules are added matter. If you start your rules by blocking all connections, then none of the rules you add later will be taken into consideration. You can check the current chain using iptables -L

If you want to insert a rule before or after another, you can use the -I directive instead of our usual -A. It takes two parameters, one is the traffic type (INPUT,OUTPUT), the other is the position where you want it to be inserted.

sudo iptables -I INPUT 1 -p tcp --dport ssh -j ACCEPT

When you run iptables -L after this command, you will see that it was added as the first rule of the input table.

run on start up

All these rules we added exist as long as we don't reboot our machine. It would be a nightmare if we had to rewrite them every single time. Fortunately we can add it to file to be ran on start up.

Later down the line, we will create a file under /etc/iptables.firewall.rules and add it to /etc/network/if-pre-up.d/firewall. This we will run every time our network is loaded.

The Loopback port

Because everyone blocks all connections except those manually added, it is common to forget a rule that is pretty much obscure to most. The loopback port.

The loopback interface, as its name implies, is a network connection a computer uses to talk to itself. It might seem redundant but it is quiet handy.

For example, I develop my web application on my local machine and I want to access it like any other website. Doing so will normally require me to recreate those protocols specially for my website, I'd have to create an HTTP request and carefully simulate the way Apache handles it. But with the loop back interface, I don't need to. All I have to do is make a request to my website on port 80, and Apache will catch it like any other request.

All I am doing is accessing a local port the same way it would be accessed externally. This is the loopback interface at work. The machine is talking to itself the same way another machine would. So it is important to make sure to enable it on the firewall.

Disabling the loopback will prevent the computer to talk to itself.

Setting up our server.

Let's create our iptable file using your favorite editor (VIM for my case):

sudo vim /etc/iptables.firewall.rules

Let's first add all the rules and I will explain them as we go:

*filter

#  Allow all loopback (lo0) traffic and drop all traffic to 127/8 that doesn't use lo0
-A INPUT -i lo -j ACCEPT
-A INPUT ! -i lo -d 127.0.0.0/8 -j REJECT

#  Accept all established inbound connections
-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

#  Allow all outbound traffic
-A OUTPUT -j ACCEPT

#  Allow HTTP and HTTPS connections from anywhere (the normal ports for websites and SSL).
-A INPUT -p tcp --dport 80 -j ACCEPT
-A INPUT -p tcp --dport 443 -j ACCEPT

# Allow connections to MySQL from anywhere (only for testing not recommended)
-A INPUT -p tcp --dport 3306 -j ACCEPT

#  Allow SSH connections
#  The -dport number should be the same port number you set in sshd_config
-A INPUT -p tcp -m state --state NEW --dport ssh -j ACCEPT

#  Allow ping
-A INPUT -p icmp -m icmp --icmp-type 8 -j ACCEPT

#  Log iptables denied calls
-A INPUT -m limit --limit 5/min -j LOG --log-prefix "iptables denied: " --log-level 7

#  Reject all other inbound - default deny unless explicitly allowed policy
-A INPUT -j REJECT
-A FORWARD -j REJECT

COMMIT

The first keyword *filter, is to describe the type of iptable we are creating, a filter table.

The next two lines, are the Loopback rules we mentioned earlier. It allows us to communicate within our own machine. The lower case i directive -i is to tell the filter to match only if the interface matches lo. -d is the destination.

-A INPUT -i lo -j ACCEPT
-A INPUT ! -i lo -d 127.0.0.0/8 -j REJECT

Next we allow connections that are already established or related using:

-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

Depending on how your application work on the server, we can also limit outbound traffic. But for now we will leave it open.

-A OUTPUT -j ACCEPT

Now that we are done with the default values, we can start setting up the rules for our needs. Since we are most likely adding these rules to our server using SSH, it would be a good idea to start with it.

-A INPUT -p tcp -m state --state NEW --dport ssh -j ACCEPT

Next, this is a web server so we will add the HTTP and HTTPS connections:

-A INPUT -p tcp --dport 80 -j ACCEPT
-A INPUT -p tcp --dport 443 -j ACCEPT

Although I don't usually recommend this, we can allow a connection to MySQL remotely. But this is just an exercise. MySQL default port is 3306, make sure to update it if you used a different port.

-A INPUT -p tcp --dport 3306 -j ACCEPT

The default port number for FTP is 20 and 21. If you modified it to be anything else, make sure the change reflects on this rule

-A INPUT -p tcp -m tcp --dport 21 -m conntrack --ctstate ESTABLISHED -j ACCEPT
-A INPUT -p tcp --dport 20 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT 

The other rules are commented, but the important one is the last which is to reject all other connects:

-A INPUT -j REJECT
-A FORWARD -j REJECT

We can now save this file and load it into the iptables rules:

sudo iptables-restore < /etc/iptables.firewall.rules;

Now you can run iptables -L and see all the current rules.

Like I stated earlier, these rules will reset next time the computer is rebooted. So let's add it as a start up file. Open or create the current file:

sudo vim /etc/network/if-pre-up.d/firewall

Let's add this content and save:

#!/bin/sh
/sbin/iptables-restore < /etc/iptables.firewall.rules

and change the permission to allow the system to execute it on start up

sudo chmod +x /etc/network/if-pre-up.d/firewall

That's it, now when we restart our computer the rules will be loaded automatically. Firewall rules are hard to grasp at first, but the good thing is it is not something you modify everyday. So feel free to bookmark this page so you can revisit the rules anytime you need them.

Reference:

I understand that it is nearly impossible to memorize all the directives that are available, the good news is you don't have to. You can find them in the manual using:

man iptables;

Here are all the commands for reference:

Cheat Sheet

  1. -A - Append this rule to a rule chain. Valid chains for what we're doing are INPUT, FORWARD and OUTPUT, but we mostly deal with INPUT in this tutorial, which affects only incoming traffic.
  2. -L - List the current filter rules.
  3. -m conntrack - Allow filter rules to match based on connection state. Permits the use of the --ctstate option.
  4. --ctstate - Define the list of states for the rule to match on. Valid states are:
    • NEW - The connection has not yet been seen.
    • RELATED - The connection is new, but is related to another connection already permitted.
    • ESTABLISHED - The connection is already established.
    • INVALID - The traffic couldn't be identified for some reason.
  5. -m limit - Require the rule to match only a limited number of times. Allows the use of the --limit option. Useful for limiting logging rules.
    • --limit - The maximum matching rate, given as a number followed by "/second", "/minute", "/hour", or "/day" depending on how often you want the rule to match. If this option is not used and -m limit is used, the default is "3/hour".
  6. -p - The connection protocol used.
  7. --dport - The destination port(s) required for this rule. A single port may be given, or a range may be given as start:end, which will match all ports from start to end, inclusive.
  8. -j - Jump to the specified target. By default, iptables allows four targets:
    • ACCEPT - Accept the packet and stop processing rules in this chain.
    • REJECT - Reject the packet and notify the sender that we did so, and stop processing rules in this chain.
    • DROP - Silently ignore the packet, and stop processing rules in this chain.
    • LOG - Log the packet, and continue processing more rules in this chain. Allows the use of the --log-prefix and --log-level options.
  9. --log-prefix - When logging, put this text before the log message. Use double quotes around the text to use.
  10. --log-level - Log using the specified syslog level. 7 is a good choice unless you specifically need something else.
  11. -i - Only match if the packet is coming in on the specified interface.
  12. -I - Inserts a rule. Takes two options, the chain to insert the rule into, and the rule number it should be.
    • -I INPUT 5 would insert the rule into the INPUT chain and make it the 5th rule in the list.
  13. -v - Display more information in the output. Useful for if you have rules that look similar without using -v.
  14. -s --source - address[/mask] source specification
  15. -d --destination - address[/mask] destination specification
  16. -o --out-interface - output name[+] network interface name ([+] for wildcard)

Resources


Comments

There are no comments added yet.

Let's hear your thoughts

For my eyes only