Linux Consultant

Routing and Firewalls with Iptables and Linux

Creating a firewall or router with IP Tables and the Linux 2.4 series kernel is a relatively simple task to do. Before the arrival of IP Tables support into the Linux kernel, the kernel was (without patches) only capable of simple firewall capabilities, similar to a cheap $20 router. Now, however, the Linux kernel comes with IP Tables support built in, resulting in a fully functional state full firewall. What are the advantages of an advanced firewall on Linux vs. commercial products?

  • An Administrator is given total control over the firewall. If needed, a Network Intrusion Detection System (NIDS) may be installed, whether on the edge of the network, or on a barrier between the network and sensitive servers/workstations. This results in an administrator being able to fully evaluate all possible attacks against the network (or attacks coming from the network, which would indicate a compromise within the network).

  • Linux firewalls, unlike some commercial solutions, have virtually no limit in users (other than the standard 254 client limit with IPv4).

  • Advanced routing, such as OSPF (a dynamic routing protocol, capable of redirecting traffic around routers which may have gone down) can be utilized (this is beyond the focus of this tutorial).

  • With only the cost of standard network cards, multiple network cards may be installed, granting multiple networks (or multiple routes in the event of hardware problems) access to the network beyond the firewall/router.

  • Updates to the Linux kernel, as well as other software, requires no flashing, saving configurations, or rebooting (unless in the event of a kernel update), unlike commercial products, which require flashing the BIOS of the router/firewall, and time taken backing up configuration of the firewall/router. This results in upgrades which are, for the most part, painless and less time consuming. Depending on the distribution, upgrades can be done automatically, whereas most commercial solutions require manual updating.

  • Since Linux is free, and a router/firewall only demands basic hardware, $300 in hardware will match the several thousand dollar commercial product in performance. Except for time required to install the system, and to get the firewall operational, this results in both lower deployment costs, as well as a lower Total Cost of Ownership (TCO). As stated before, commercial products require more attention to update, whereas a Linux router can be updated automatically.

  • Only the more advanced (and therefore more expensive) commercial routers/firewalls come with SSH capabilities, whereas Linux comes with the OpenSSH server, which increases security over the wire, making remote management practically as secure as being at the remote firewall/router.

  • VPN servers are available for Linux, in both the standard IPsec protocol, as well as the PPTP protocol. Again, this subject is beyond the scope of this tutorial.

Firewalls/routers require very little hardware to run on, and the specific hardware depends on your needs. For instance, if you were simply setting up a home firewall/router, a Pentium-class computer operating at 120 MHz with about 64 MB of RAM will suffice. For corporate needs, however, a computer with a Pentium II 300 MHz processor with 128 MB of RAM will be more than enough. The speed of the processor, and the amount of RAM directly relates to the amount of traffic through the firewall/router, and the amount of firewall rules that are written to filter that traffic. For the HNSG network, I personally use a Pentium class computer at 133 MHz with 64 MB of RAM for the edge firewall, and a 300 MHz AMD K6-2 computer with 128 MB of RAM for internal firewalling/routing. Your needs may be higher, or less, depending once again on the traffic encountered, and the amount of firewall rules used.


First of all, start out with a fresh install of the distribution of your choice. I personally prefer Debian GNU/Linux over other distributions, but you may decide to go with Red Hat or SuSE. Regardless of the choice of your distribution, all the steps below, as well as all the commands are all the same. Note, however, that Debian does not come with the Linux kernel 2.4 series by default, a simple command, however, will rectify this:

apt-get install kernel-image-2.4.18-1-386

will install the proper kernel, assuming you have a i386-based system. If not, substitute your architecture here.

Once you have the 2.4 series kernel installed, you will see a warning about inserting a line that reads:


Insert this line by editing the file with vi, or vim:

vi /etc/lilo.conf

Then, you may scroll down to the first stanza (labeled as Linux) and enter the desired line, by pressing 'i' before typing where desired. Quit vi by pressing ':wq' with the colon. Next, re-run lilo (making sure that the new kernel will load) by running:

/sbin/lilo -v

A good tip to remember is that if you are remotely restarting the router, you should set the 2.2 series kernel as the default, and then rename the 2.4 series kernel as 2.4. Then, run lilo like so:

/sbin/lilo -R 2.4

Which will load the 2.4 series kernel the first time. If you cannot get access to the router because of a kernel panic issue, you can have someone remotely reboot the router. The router will then come up running the 2.2 series kernel.

Once the 2.4 series kernel is running properly on the router, you should enable IP forwarding, which allows the interfaces to route packets back and forth. To do this, enter the following on a command line:

echo 1>/proc/sys/net/ipv4/ip_forward

or, depending on whether the /proc/sys/net/ipv4 directory exists:

echo 1>/proc/sys/net/ip_forward

/sbin/iptables -t nat -A POSTROUTING -o eth1 -j MASQUERADE

The first command allows forwarding of packets, while the second command allows IP Masquerading of packets, which to make a long story short allows packets that are forwarded to be altered so that the source headers in the packets are modified to appear as if they originated from the router itself.

This can of course be automated, to allow these commands to be ran at startup. The easiest way to do this is to create a /etc/init.d/ shell script, and insert the above lines into it. To enable the shell script to be ran at startup, on Debian GNU/Linux, you can enter the following command:

update-rc.d defaults

Which will create symbolic links to this script at startup, so that these commands are ran. Also, you might want to add more Iptables rules, as demonstrated later on. With Red Hat Linux, you can add the following line to /etc/rc.d/rc.local or /etc/rc.local (/etc/rc.local is typically on older Red Hat distributions, such as 6.2):


And the result will be the same under Red Hat as well as Debian- your router script will start at a system restart or startup, enabling the system to route packets as desired. You should now have a working router. The only function this router is missing now is DHCP server capabilities.


The DHCPD server is a server that allows DHCP clients to connect to the server, and request IP addresses and gateway/dns information. DHCP is used in most large networks as a means of easily managing IP addresses. Linux has a server of its own, creatively called DHCPD. DHCPD is available from the Internet Software Consortium's website at The server should also be available from your distribution however, so check with your distribution first.
ISC only supplies standard tarball packages, so if your distribution does not supply the DHCPD package, you will have to use the ISC's package. Download the file and extract it using the following two commands:

gunzip dhcpd-version.tar.gz
tar -xvf dhcpd-version.tar

Make sure to replace version with the actual version. Now, perform the following commands:

cd dhcpd-version
make install

DHCPD should install flawlessly, if not then you should complain to the mailing list on the ISC's website. We will only have to perform three tasks with DHCPD, the first is to edit the configuration file. Place the following text in your /etc/dhcpd.conf file:

DHCPD should install flawlessly, if not then you should complain to the mailing list on the ISC's website. We will only have to perform three tasks with DHCPD, the first is to edit the configuration file. Place the following text in your /etc/dhcpd.conf file:

# /etc/dhcpd.conf by Christopher Pace
ddns-update-style ad-hoc;
default-lease-time 259200;
max-lease-time 300000;
option subnet-mask;
option routers;
option domain-name-servers;
subnet netmask {

Of course you will want to substitute the routers, domain-name-servers, netmask, and range with what is for your network. For instance, I have a network that I use DHCP to assign a total of 60 IP addresses. This range is from .20-.40, and from .50-.90. DHCPD will only assign IP addresses within this range, as I like to keep .1-.19, .41-.49, and .91-.254 free for servers and such. The 'default-lease-time' and 'max-lease-time' settings are used to specify how long the DHCP lease will last if the client doesn't request extra time (default), and if it requests the max time (max). This time is in seconds. If you want to have a static IP assigned to a host, then you can use the following syntax in your /etc/dhcpd.conf file:

host Joe {
hardware ethernet 00:c0:f0:25:b7:15;

This will assign the IP of to Joe each time that it requests an IP. The MAC address is the 'hardware ethernet' address.
Now then, we move along to the next step, creating the /var/state/dhcp/dhcpd.leases file:

touch /var/state/dhcp/dhcpd.leases

Now, we will start DHCPD, to test it out. First, if you are currently using another DHCP server on your network, disable that one. Next, run the following:


Finally, start up a DHCP client (if you are using Windows 98/2000/XP/NT, you can use the ipconfig command to release the IP and then renew it by typing:

ipconfig /release_all
ipconfig /renew_all

Please note that for Windows 2000 and XP, you should use release and renew without the “/all”.
This should take a while, as the DHCP client is searching for the original server. After a while, it will time out, and then query the network for any DHCP servers, finding our Linux one. Now, once you are sure that DHCPD works, we should create an init script for DHCPD. This is used to start, restart, and stop the DHCPD service. Also, this init script will be automatically run at boot to start DHCPD. Place the following text in /etc/init.d/dhcpd:

# /etc/init.d/dhcpd file by Christopher Pace
case "$1" in
echo -n "Starting DHCPD Daemon: dhcpd"
start-stop-daemon --start --quiet --exec /usr/sbin/dhcpd
echo "."
echo -n "Stopping DHCPD Daemon: dhcpd"
killall -9 dhcpd
echo "."
echo -n "Restarting DHCPD Daemon: dhcpd"
killall -HUP dhcpd
echo "."
echo "Usage: /etc/init.d/dhcpd {start|stop|restart|reload|force-reload}" >&2
exit 1
exit 0

You should now:

chmod 700 /etc/init.d/dhcpd
update-rc.d dhcpd defaults< FOR DEBIAN SYSTEMS

If you run a different system then listed, you should check with your distribution on how to properly tell the system to use the init file you just made. On some systems, simply chmod-ing the init file will work. Also, some systems only have a /etc/rc.d directory, where the init file should be placed in the run levels associated with startup, halt, and so forth. Read the FAQs that your distribution has as to which run levels are for which tasks, as some distributions tend to go against POSIX.
DHCP is a useful client, but when routers are shipped with DHCP server capabilities, too often the DHCP server is stripped-down, leaving the many options that DHCPD offers missing. Thus, it is necessary to have DHCPD instead of these stripped-down servers, in order to satisfy particular needs. For instance, I need 60 DHCP-assigned addresses, in two different IP ranges. Thus, I would recommend DHCPD for anyone who needs a truly customizable DHCP server.


Once DHCPD has been installed and setup, the only other task left is securing the router. Nmap, a very useful port scanning tool, can assist you in checking your security from the outside, as can several popular online port scan utilities. To block a specific port, issue the following command:

/sbin/iptables -A INPUT -p tcp -i eth1 --dport xxx -j REJECT --reject-with tcp-reset

In this example, replace "xxx" with a desired port number. In this example, you will only block outbound accesses to the desired port from the outside, not the inside. For all Iptables rules, case sensitivity does matter. To block both, simply omit "-i eth1". For instance, if port 135 (NetBIOS) needs to be blocked only on eth1 (the external interface), the following command will filter this port:

/sbin/iptables -A INPUT -p tcp -i eth1 –dport 135 -j REJECT --reject-with tcp-reset

There are essentially three different targets (the -j part specifies which target a packet matching these rules goes to) when dealing with blocking/allowing communications:

  • ACCEPT (Allows communication)

  • DROP (Drops the packet, which results in a large delay when port scanned. This, if made against a large amount of ports, will slow a port scan very much. When port scanned, the target will appear as “filtered”, meaning that the port scanner was unable to connect to the desired port, but that there is a rule prohibiting it, usually meaning that the port is firewalled.)

  • REJECT (Rejects the packet. If this is used with --reject-with tcp-reset, the port will appear “closed”, meaning that the port scanner thinks that there are no services behind the router on that particular port. If Reject is used by itself, the port will appear as “filtered”.

For instance, lets suppose that we have a firewall with a SMB server behind the firewall. The SMB server runs on port 139. If we ran a port scan against our firewall (the external IP address in this example will be, and the internal IP address will be A port scan from the outside (with no filtering) will appear as this:

$ nmap

Starting nmap V. 2.54BETA31 ( )

Interesting ports on localhost (
(The 1551 ports scanned but not shown below are in state: closed)
Port State Service
139/tcp open netbiosssn
Nmap run completed -- 1 IP address (1 host up) scanned in 0 seconds

We will now add the following rule to our firewall:

/sbin/iptables -A INPUT -p tcp -i eth1 --dport 139 -j DROP

This will result in the port 139 being filtered:

$ nmap

Starting nmap V. 2.54BETA31 ( )

Interesting ports on localhost ( (The 1551 ports scanned but not shown below are in state: closed) Port State Service 139/tcp filtered netbios-ssn Nmap run completed -- 1 IP address (1 host up) scanned in 0 seconds


Now then, if we were to add the following rule to our firewall (first deleting the old rule):

/sbin/iptables -D INPUT -p tcp -i eth1 --dport 139 -j DROP

/sbin/iptables -A INPUT -p tcp -i eth1 --dport 139 -j REJECT --reject-with tcp-reset

$ nmap

Starting nmap V. 2.54BETA31 ( )
Interesting ports on localhost (
(The 1551 ports scanned but not shown below are in state: closed)
Nmap run completed -- 1 IP address (1 host up) scanned in 0 seconds

As you can see, all ports are now “closed” to incoming connections on the external interface. Let's break down the syntax of these iptables commands, the last command will be our example.

First of all, Iptables stores firewall rules in several groups, the most common being INPUT, OUTPUT, and FORWARD. Before you tell Iptables what to filter, you should first specify which group, or table (previously called chain, with ipchains) you wish to modify. When modifying a table, you should specify where in the table the new rule will be. All tables are processed from the beginning of the table, to the end. Any rules allowing or denying packets in the beginning will take precedence over any rules in the end. What this means is that if you have the first rule in the INPUT table to reject all traffic, no network access will be allowed. However, if you have this rule in the end of the INPUT chain, you can specifically allow certain protocols and traffic, in the beginning of the chain. This is by far the best way to run a router. What this means is that all traffic not specifically allowed, will be rejected.

When modifying a chain, you have three different ways to modify it, you can either Delete, Append, or Insert a rule. The abbreviations for these actions are thus D, A, and I. Delete obviously deletes, or removes, a specific rule in the specified table. Append creates the given rule at the end of the table, meaning that this rule will be processed last. Insert creates a given rule at the very beginning of a table, thus meaning that this rule will be processed first, and if a packet matches, no more rules will be examined for a particular packet.

The -p option used specifies which protocol this rule will pertain to. There are essentially three protocols to worry about. These three protocols are TCP, ICMP, and UDP. TCP is by far the most commonly used protocol. TCP is used by applications such as HTTP, SSH, FTP, Telnet, and SMB, just to name a few. ICMP is a rather useless protocol, in that no applications transmit their data using it (there are some exceptions, but these are most commonly novelty applications, which are used as jokes). ICMP is mainly used for ping requests, and ping responses. If someone runs a traceroute against your router, and your router replies, your router has ICMP enabled. ICMP is usually more of a hassle than what it is worth, because “script kiddies” can use ICMP against you, causing what are known as ping floods, or an enormous amount of ping packets, targeted at a host. This is a questionable vulnerability, however, because even if your router does block these requests, and does not answer to them (which would then saturate upstream bandwidth), the ping requests alone will saturate your downstream bandwidth, resulting in your server not being able to respond to legitimate client requests. While ping flooding used to be a rather new attack, it is no longer now considered one, and is normally only launched from a group of “zombie computers”, which are computers which a person has taken control of, often illegally. The decision to block ICMP or not is up to you. UDP is a protocol that is used in a select few applications, because UDP is extremely bad with handling dropped packets, unlike TCP, which is designed to be used even under heavy packet loss. UDP is most commonly used in LAN environments, but does have its uses on the Internet. An example of UDP communication would be NFS. Until very recently, UDP was the only protocol for NFS, however NFS recently has TCP support. I do not recommend running a UDP server, if a TCP/IP server is available for the same purpose. TCP/IP does have a small amount of overhead, but it is well worth it for the redundancy, error checking, and dropped packet implementations in TCP.

The -i option specifies which interface to use. To list all available interfaces, use the ifconfig command with no arguments. The most commonly used interfaces on a router are eth0 (first network interface card), eth1 (second network interface card), and lo (the loopback device, which is always assigned the IP address The lo device is always present, and is only used for applications which require a network connection (such as the X server, a graphical user interface server).

The --dport means destination port. We have gone over this before.

Lastly, the -j, or jump, option specifies what to do with a packet once it has met the stated requirements. Typical uses are REJECT, DROP, ACCEPT, and FORWARD. FORWARD will forward the packet to another table for additional inspection. The ACCEPT target specifies that the packet will be accepted, or allowed, and no further inspection will occur to the packet. The REJECT and DROP targets we have already covered.

There are, additionally, some other options you should be aware of. The first additional option is -s, which specifies the source address. This is useful in blocking specific hosts from specific services. Mainly, this is used in special circumstances, where a server will never have a legitimate reason to connect to a specific host. An example of this would be if there is a file server on your LAN, or exposed to the Internet, which will never have a reason to communicate to, say, a print server, or a web server. Another commonly used application of this option is to block all communication to and from a malicious host. This is used when a host is attacking a server or router, as a quick way to contain damage caused by a given host. Some administrators choose to block all protocols, on all ports, from any host which attempts any exploit or attack against a server or router. While this is a good idea, it generally is not needed, as most hosts attempting to attack a server (specifically a web server) are usually worms. While these worms are annoying (and this is of course only if a server is not vulnerable to this worm), administrators soon find that sometimes this can mean blocking literally thousands of IP addresses per day. However, the case can also be made that the worm is wasting bandwidth to a server. I generally block hosts which attempt exploits, unless there is a worm in its “Zero Day” stage, which means the worm was just released. The reason I do not add special rules to block these hosts is because of the sheer amount of attempts, often in the thousands per server range. In the case of “Zero Day” worms, you can block the specific exploit attempt through iptables as well, depending on how the exploit functions.

The -d option is used to specify the destination IP address, which is useful in filtering outgoing traffic (normally used in the OUTPUT chain), to specific hosts. For instance, adding an iptables rule to block outgoing traffic to a website that tries to steal user information (such as past attempts to gain sensitive user information, such as credit card information, through an email pretending to be from a given organization, such as VISA and Pay Pal). The -d option can also be used to fine-tune trust relationships within a LAN. For instance, placing a block on a destination that is a file server between two LAN segments, that users behind a firewall have no need to access, will prevent a malicious user or a virus from attacking the protected file server. This could be done both on the file server, and the router, but is most commonly done in special circumstances when the file server, or any server for that matter, does not have a sufficient access control policy. A better example of this would be a Jet Direct printer (which is a network printer card made by HP). Typically, network devices such as these have either no access control policies based on a packet-by-packet level, or the access control policy is too weak for secure usage.

Now we will cover packet “forwarding” between an outside host, and a server than lies behind a firewall. To do this, we use a special table, called the nat table. This is the same table that is used to enable IP Masquerading, which was explained earlier. We will use the PREROUTING chain within the nat table to accomplish this:

/sbin/iptables -t nat -A PREROUTING -p tcp --dport 80 -i eth1 -j DNAT --to

This iptables command forwards all requests on port 80, on the second network interface, to the server at IP address on the local network interface. Note that with multiple network interface cards, you only need to specify the destination IP address. The DNAT destination table stands for Destination NAT, and is usually only used to “forward” ports between interfaces. Note that if you swap eth0 for eth1 in this example, it will forward all HTTP requests (HTTP runs off of port 80) to another host. This may be useful in some environments to either prevent hosts inside the firewall to “surf the web”, or to forward requests to a proxy server. If you need to forward ports that are not equal (for instance, port 8080 on the external firewall needs to be “forwarded” to on port 80), use instead of just the IP address.

Once you have allowed specific communications to occur (for instance, the “port forwarding” example above), it is necessary to further lock down your firewall, by blocking all requests not specifically allowed. To do this, use the following three rules:

/sbin/iptables -A INPUT -i eth1 -p tcp -m state --state ESTABLISHED,RELATED -j ACCEPT

/sbin/iptables -A INPUT -i eth1 -p tcp -j REJECT --reject-with tcp-reset

/sbin/iptables -A INPUT -i eth1 -p udp -j REJECT

Alternatively, if you have UDP-based servers or clients, create another rule just like the first, except changing tcp to udp.

These two rules are essential to running a secure firewall (which is the point of even running a firewall, because a firewall is useless if it is not secure). The first rule uses the -m option to specify that all packets which are in a state of established or related, are accepted. One of the major advantages over ipchains (a previous filtering utility for the Linux kernel) is this option, which means that the firewall examines packets based on connections, and not blindly examining each packet by itself. This is needed so that clients within the firewall can access websites, because a client connection to a website opens a port on the firewall for outside communication. This also allows packets which are related to a connection initiated from within the firewall, for instance when a server responds to a request by stating that the client should receive a file (over FTP) by accepting communication on a different port. If this rule is omitted, the firewall won't be of much use, even if it is only protecting one server, as the server will probably need to communicate to external servers as well (such as a DNS server).

The second rule is quite simple, as is the third. These two rules basically state that all packets will be blocked if they are tcp or udp. It is imperative that you allow desired communication before entering these two commands. As a standard precaution, make sure that you can get local access to the firewall to flush the iptables rules (/sbin/iptables -F).

Once you have your desired rules for your firewall, you have two options to preserve them on the next reboot. You can either use the built-in iptables save feature (which is still technically developmental, and may not work), or you can add the rules you want to /etc/init.d/ as described above. Which option you pick depends on your own preferences. Some administrators don't trust the iptables save feature, and some do trust the feature. That being said, I personally have used both methods, and the method that is by far the easiest to manage is the iptables save feature. However, it is also very easy to do this:

echo “/sbin/iptables -A INPUT ...........”>>/etc/init.d/

Which will append the rule to the /etc/init.d/ script. Should you choose to save your iptables rules, you can easily save them by:

/etc/init.d/iptables save active

Which will save all active rules. On some systems, you may have to add the following line to /etc/init.d/

/etc/init.d/iptables load active

You may have to do this because different distributions have different options of doing a load of the active tables, since it is still technically in development. On these systems, iptables will instead load with no rules whatsoever. Check your documentation, but it is safe to add the line to /etc/init.d/ regardless.


Linux is extremely useful as a firewall and a router, even in the most complex environments. Linux is extremely light on system resources, however, if a firewall has a large amount of rules on it, traffic will be slowed down considerably if a system is not fast enough. That being said, a Pentium-class CPU will more than suffice for most circumstances. If you are in doubt, run the top command to view CPU utilization during high amounts of traffic, and upgrade accordingly. If you have any questions or comments on this tutorial, you may reach me at the email address and website at the top of this document. There are many more tutorials of this nature on the site above