Building your own Router with a Raspberry Pi

Building your own Router with a Raspberry Pi

designing and building a raspberry pi router

Running a custom router gives unprecedented insight into everything happening in a network. Building your own router with a a Raspberry Pi may be a little daunting, but it’s surprisingly easy and rewarding to do… and the benefits are tremendous.

Many times, I’ve been able to diagnose problems with the internet more effectively thanks to the Rapsberry Pi router. No matter what the problem is — from slow internet to devices that cannot connect — a custom router will offer more insight into what’s going on (via logs), and more support (via the community).

This project will introduce key networking concepts and give you the tools to debug problems with a home network quickly. It’s a low-intermediate project with a Raspberry Pi, and assumes some basic knowledge of Linux. It was tested with Raspbian Buster Lite, but should work with all modern Debian distributions. It enables this related project (which more experienced readers may wish to skip to):

Building your own Router

A Raspberry Pi 4 is a quite capable router in the right circumstances.

But first, let’s be clear on terms.

A switch shuffles data around the network. A router helps direct that traffic.

Building a “router,” in this context, means that we will be implementing DHCP, DNS, and a Firewall.

If you don’t know what any of that means, don’t worry. For now, let’s just assume that you have a Raspberry Pi 4 (see parts list at the bottom). The “version 4” bit is important because it has a significantly better network card (eth0) than prior models (Gigabit). This points at the biggest limitation of using the RPi as a router: the bandwidth is capped at 1000 Mbps.

Bandwidth and throughput are often confused.

The band “width” is the maximum amount of data which may flow through the system (in this case, from the home to the internet). Throughput measures how much bandwidth is used at any moment.

Incidentally, this guide should work for any Debian-based linux system that you want to turn in to a router. You could use better hardware to create an even more effective solution. I chose a Pi4 because our internet speed is only 30Mbps anyway (DSL). As I described in the network monitoring post, this Raspberry Pi 4 is proven to be way more than enough for this case. The following are the exact products I used.

If you're new to Raspberry Pi, the popular CanaKits are a great place to start. I prefer to buy the Raspberry Pi 4, power adapter, micro SD cards, and heatsinks separately. Not only is this cheaper, but it reduces e-waste.

The following are affiliate links to other parts I used in this project. I never link to a product that I have not personally used.

Have a DSL/PPP connection?

Read about the specific quirks of using a Raspberry Pi as a DSL router.

A second ethernet port is required (see the next section). I used the USB 3.0 Gigabit connector, above.

Connecting to the Internet

I will refer to the network interfaces as wan and lan.

If you want things to be copy-and-paste, you can rename your network interfaces to match. For example, lan is the name for the fastest of the two interfaces (the onboard network interface, eth0, in my case). To create the static name, first type ifconfig get the MAC address for the interface (after ether). If you found ether aa:bb:cc:dd:ee:ff, then create or edit /etc/udev/rules.d/10-network.rules to include:

SUBSYSTEM=="net", ACTION=="add", ATTR{address}=="aa:bb:cc:dd:ee:ff", NAME="lan"

… and then repeat the process for the wan interface.

The internal network (LAN) needs to deal with more throughput than the WAN.

This is why the faster interface is chosen for the LAN.

At this point, you can directly connect the wan interface to the modem provided by your ISP. You may want to put the modem into transparent bridge mode, if possible, since the Pi is taking over the routing functionality (more on this).

While you’re here, take a moment to make sure the WAN is configured correctly to obtain a DHCP lease. If you use some method other than standard DHCP (e.g., PPOE), you will need to modify this step according to your needs. But in most cases, it is sufficient to create a file at /etc/network/interfaces.d/wan with the following contents:

auto wan
allow-hotplug wan
iface wan inet dhcp

DHCP Server

With the Pi online, it’s time to let devices connect to the Pi.

In this example, the Raspberry Pi router will have an IP address of 192.168.0.1.

Be careful if you change this IP!

This example uses the 192.168.0.0/24 subnet. If you decide to change this, make sure you choose from the range of valid private subnets.

This means first creating or editing /etc/network/interfaces.d/lan:

allow-hotplug lan
iface lan inet static
        address 192.168.0.1
        netmask 255.255.255.0
        gateway 192.168.0.1

Now the LAN interface will have a static IP and treat itself as the router. Now it’s time to make it capable of assigning IP addresses to other devices on the network. Running a DHCP server will therefore make it so many computers can connect to the Pi.

First, disable dhcpcd and install isc-dhcp-server:

sudo systemctl disable dhcpcd
sudo apt install isc-dhcp-server

Then edit /etc/default/isc-dhcp-server, replacing INTERFACESv4:

INTERFACESv4="lan"

We’ve told the DHCP server what interface contains the LAN. Now it needs to know the parameters for the home network. To continue with the example, we will let the server assign IP addresses in the range from 192.168.0.100 to 192.168.0.199 (more on why in a moment). Edit /etc/dhcp/dhcpd.conf to first change the domain name and domain name servers:

option domain-name "router.local";
option domain-name-servers 8.8.8.8, 1.1.1.1;

The .local suffix is convention for a LAN, but you’re welcome to replace the router name. The domain name servers will be changed soon, but are pointed at google for now.

Also add add the following:

authoritative;
subnet 192.168.0.0 netmask 255.255.255.0 {
        range 192.168.0.100 192.168.0.199;
        option routers 192.168.0.1;
        option subnet-mask 255.255.255.0;
}

This tells the DHCP server that it may assign IP addresses to the devices which connect to it within the given range. The reason for the relatively small range is that we can assign static IP addresses outside that range. For example, you could take the same MAC address for the lan above and add the following section:

host router {
        hardware ethernet aa:bb:cc:dd:ee:ff;
        fixed-address 192.168.0.1;
}

Adding the static IP for the router, as well, means that the DHCP server can never get confused and accidentally assign a different IP address. In general, encoding the IP addresses in the DHCP server also centralizes the authority (rather than coding static IPs on specific devices, which is far more brittle IME).

If you restart the Raspberry Pi now and connect a computer to the lan port, you should find an IP address in the appropriate range. Using Mac OSX, I opened the Network Preferences pane, and then the Hardware tab to find the MAC address of my laptop’s USB-C to Ethernet adapter. Then I assigned a static IP (just like above) to that address and restarted the DHCP server:

sudo systemctl restart isc-dhcp-server
connect to raspberry pi router
The MAC address came from the Hardware tab. Pressing the “Renew DHCP Lease” button will ask the DHCP server for a new IP address, effectively refreshing the connection.

Even though the fixed-address is outside the range above, this sort of static IP assignment will work with any device. I like to give static IPs to all my known, trusted devices that are outside the public range. This does the following:

If for some reason a computer can ever not connect to the home network, running your own DHCP server makes things easier to debug. Running journalctl -xe | grep dhcpd shows any attempts by devices to connect to the home network:

May 03 15:50:44 router dhcpd[2648]: DHCPREQUEST for 192.168.0.106 from xx:xx:xx:xx:xx via eth0
May 03 15:50:44 router dhcpd[2648]: DHCPACK on 192.168.0.106 to xx:xx:xx:xx:xx via eth0

And taking a peek at /var/lib/dhcp/dhcpd.leases reveals what devices have connected lately.

Connecting WiFi Devices

For this, you will need a WiFi router or access point.

The typical approach is to use the on-board WiFi. This mostly matches the above steps, except using the wlan0 device on the Raspberry Pi for the lan interface and setting up hostapd. However, that is not recommended in the context of this post. Letting the Raspberry Pi serve as a pass-through between two ethernet interfaces is a simpler and faster approach.

Instead, simply connect a WiFi router to the lan port on the Raspberry Pi and then put the router into Access Point (a.k.a. bridge) mode. Now, the router won’t care if clients connect via ethernet or WiFi.

I’m a big fan of the Orbi routers — not only are they extremely fast, but you can plug the base station’s “internet” port into the Raspberry Pi and have a dozen wired connections at your disposal around the house:

Forwarding Traffic

At this point…

  • The Raspberry Pi can talk to the internet (WAN).
  • The devices on the LAN can talk to each other.

… but there is no communication between the two.

Enter firewalld:

sudo apt install firewalld

Firewalld is the easiest solution I’ve found so far.

IP tables and firewalls can be a pain.

At a minimum, you will need a home and public zone, and then to masquerade the traffic between the two. This just tells the firewall that the lan interface is for the home, and the wan/ppp0 interfaces are for the public.

sudo firewall-cmd --zone=home --add-interface=lan
sudo firewall-cmd --zone=public --add-interface=ppp0
sudo firewall-cmd --zone=public --add-interface=wan
sudo firewall-cmd --zone=public --add-masquerade
sudo firewall-cmd --zone=home --add-service=dns
sudo firewall-cmd --zone=home --add-service=dhcp

At this point, computers connected to LAN should be able to access the internet. You could just run sudo firewall-cmd --runtime-to-permanent now to save it all, but there may be a few more useful steps (below). If at any time internet (or other) traffic is not working from within the network, run journalctl -xe and look for lines like this:

FINAL_REJECT: IN=ppp0 OUT= MAC= SRC=77.247.109.40 DST=...

In this case, the rule is working as expected. Someone from an unknown IP address attempted to contact my home network through the ppp0 interface (i.e., from the internet). In other cases, you may need to open up the firewall to allow certain traffic.

Firewalld is easiest when using zone-based rules. You will need at least two zones, which I have called home and public instead of lan and wan (to match firewalld’s conventions). I also have a third (optional) trusted zone in my configuration for the kubernetes/docker subnet. So when I type firewall-cmd --get-active-zones, I see:

home
  interfaces: lan lo
public
  interfaces: wan ppp0
trusted
  interfaces: docker0 cni0
  sources: 10.244.0.0/24 10.202.0.0/24 10.96.0.0/24

The definitions for zones are contained in xml files located at /etc/firewalld/zones/:

<?xml version="1.0" encoding="utf-8"?>
<zone>
  <short>Public</short>
  <description>For use in public areas. You do not trust the other computers on networks to not harm your computer. Only selected incoming connections are accepted.</description>
  <interface name="wan"/>
  <interface name="ppp0"/>
  <service name="ssh"/>
  <service name="http"/>
  <service name="https"/>
  <masquerade/>
</zone>
<?xml version="1.0" encoding="utf-8"?>
<zone target="ACCEPT">
  <short>Home</short>
  <description>For use in home areas. You mostly trust the other computers on networks to not harm your computer. Only selected incoming connections are accepted.</description>
  <interface name="lan"/>
  <interface name="lo"/>
  <service name="ssh"/>
  <service name="mdns"/>
  <service name="samba-client"/>
  <service name="dhcpv6-client"/>
  <service name="dhcp"/>
  <service name="dns"/>
  <service name="http"/>
  <service name="https"/>
  <port port="5000" protocol="tcp"/>
  <port port="3000" protocol="tcp"/>
  <port port="9999" protocol="tcp"/>
  <port port="10250" protocol="tcp"/>
  <port port="10250" protocol="udp"/>
</zone>
<?xml version="1.0" encoding="utf-8"?>
<zone target="ACCEPT">
  <short>Trusted</short>
  <description>All network connections are accepted.</description>
  <interface name="docker0"/>
  <interface name="cni0"/>
  <source address="10.244.0.0/24"/>
  <source address="10.202.0.0/24"/>
  <source address="10.96.0.0/24"/>
  <service name="dns"/>
  <service name="dhcp"/>
  <service name="dhcpv6-client"/>
  <service name="https"/>
  <service name="http"/>
</zone>

Traffic is categorized into zones based upon the interface or source, such as:

  • sudo firewall-cmd --zone=public --add-interface=ppp0
  • sudo firewall-cmd --zone=trusted --add-source=10.202.0.0/24

And a zone can receive traffic to a service or a port:

  • sudo firewall-cmd --zone=public --add-service=http
  • sudo firewall-cmd --zone=home --add-port=10250/tcp

Note that these changes are temporary. This allows for safe testing. To make them permanent, run sudo firewall-cmd --runtime-to-permanent. If there’s not a If you need more help with firewalld, check out this article.

DNS Server & Rewrites

A DNS server can make this little router even better.

The job of the DNS server is to respond with an IP address for a given domain name. For example, it knows what IP address your computer should connect to in order to reach google.com. Many people like to run their own for security and privacy reasons.

There are ad-blockers out there, for example, that work as DNS servers. By installing Pi Hole or AdGuard Home, you can prevent computers on the network from being able to locate advertising/spam/malware domains. Both of these two tools also support DNS rewrites, so that you can change the “location” of any site.

Once you’ve installed one of the two, just edit /etc/dhcp/dhcpd.conf: option domain-name-servers 192.168.0.1;

Now, all devices that connect to the network will be told to use this DNS server.

Why are rewrites so useful? Check out the following post on building a home server. Rewrites let the same URL be used internally and externally, enabling HTTPS anywhere. For example, by using AdGuard Home to rewrite *.snowy-cabin.com to 192.168.0.100, any traffic that originates within the home network to house.snowy-cabin.com will never leave the home network. This means that the same https:// URL can be used to reach the reverse-proxy, where SSL termination happens, ensuring secure communication for both public and private traffic.

post id not found: #8977

Cheat-sheet

To recap, here are some key concepts for home servers:

  • Static IPs: edit /etc/dhcp/dhcpd.conf to add a new host, then restart isc-dhcp-server and renew the lease on the client.
  • Port forwarding: run sudo firewall-cmd --zone=public --add-forward-port=port=443:proto=tcp:toaddr=192.168.0.100
  • Custom domain names: use the DNS server to make your unique domain name work internally and externally.
  • Monitoring network traffic: instructions.

Home Automation Ideas

Thanks for reading this post. You might be interested in this list of 100+ home automation ideas.

Or, drop your email in the form below and you'll receive links to the individual build-guides and projects on this site, as well as updates with the newest projects.

... but this site has no paywalls. If you do choose to sign up for this mailing list I promise I'll keep the content worth your time.

Written by
(zane) / Technically Wizardry
Join the discussion

25 comments
  • Hi,

    Thanks a lot for this tutorial! It matched exactly what I was looking for (along with network monitoring tutorial). However, I am hitting some snags (in both).

    I have connected a WiFi router to lan interface as mentioned above. I have an android phone connected to the said router. The phone is able to connect to the internet. However, no other device connected to the WiFi is able to access internet. The router is in Access Point mode.
    I am able to access Pi via WiFi.
    I have setup a Pi Hole. But it is not showing any requests from the connected android phone. The DNS seems correct as websites are easily accessible.
    It would be great if I can understand what is going on.

    • You mentioned setting up a Pi Hole. I would highly recommend de-activating that until you have the router working correctly. From your description of the problem, it sounds like a DNS resolution issue (which is why I suspect pi-hole). If you looked at your Android’s system settings (not sure where, don’t have one) it would likely report a different DNS server than the other devices on the network. In other words, when one device has internet access and another does not, I almost always suspect DNS issues.

      Your goal is first to make the router broadcast a non-local DNS server via DHCP, such as 8.8.8.8. Once you’re sure that’s working (i.e., all devices on the network report this as their DNS server), then try switching that DNS sever over to the local PiHole install.

      • Thanks! I am afraid I am hitting some walls before that.
        After setting up the DHCP server, and checking if laptops/smartphones connected to RPi’s LAN is OK, I restart the RPi. However, on restart only LAN and LO interface are present.
        I have to enable dhcpcd to see WAN interface (and thus connect RPi to internet). Further, my LAN is not able to connect to the internet.
        Current situation: LAN is up. RPi can see WAN interface, thus connected to the internet.
        Note: I enabled dhcpcd. Only then this will happen, else only LAN interface observed.
        But none of the devices in LAN are connected to the internet. I have not installed PiHole or any DNS rewrites.
        Any help would be great. 🙂

        • Both of those two issues sound like isc-dhcp-server is not working correctly. Its job is to take the place of dhcpcd, and it should enable the other devices on the LAN to have internet. What do its logs say? Have you tried journalctl after boot to look for any failures?

          • I can share the logs. How can I do that here?
            I had a clarification. After the following step:

            In this example, the Raspberry Pi router will have an IP address of 192.168.0.1. This means first creating or editing /etc/network/interfaces.d/lan:
            ….

            Shouldn’t we do something similar for wan interface? Assign a static IP with gateway of my internet modem like this:
            (file name: /etc/network/interfaces.d/wan)
            allow-hotplug wan
            iface wan inet static
            address 192.168.3.16
            netmask 255.255.255.0
            gateway 192.168.3.1

            When I did this, now wan interface is up on reboot. I can connect to the internet too!
            However, there is no internet access on LAN.

          • Ah, I see. You didn’t put your modem in bridge networking mode, so you actually have two routers running. This is why you had to create the WAN file. Your “Pi Router” is connecting to your “Modem Router,” and you have 2 separate LANs. What you’ve called the WAN is actually a LAN running on the 192.168.3.0/24 subnet. There’s nothing wrong with this per se, but you will not see any speed benefits that might be observed if the modem’s built-in router were disabled. Btw, feel free to use the contact form to get in touch directly via email so you can attach logs and such 🙂

          • Success! Lack of internet was a DNS issue!
            Now my devices are connecting!
            So I just needed to add the /etc/network/interfaces.d/wan file.. 😀

  • I am looking to implement this on my home network. So far just one question (probably more to come), why did you choose firewalld over ufw? Thanks!

    • Hey! I chose firewalld because I found it much easier to configure, given the zone-based rules described in this article, which made the whole thing infinitely easier to reason about. Plus, the syntax for the zones is encoded in human-readable XML files, as you see in the article, and it’s extremely easy to come back and understand what’s going on.

      I actually tried using UFW originally (which does not have either of these features). I ended up royally screwing up the network several times (completely unusable for any traffic, at one point I couldn’t even SSH in to the router). I had to re-install UFW, at which point I had lost all of my config since it’s not stored in easy XML files. I eventually had UFW running for a week, but was constantly debugging it. Maybe I just didn’t spend enough time with UFW docs, but it took me just minutes to switch to firewalld and I’ve never had to touch it once.

  • Zane, I think this article points in the right direction to help me achieve a wifi direct (p2p) setup with a pair of pi’s. I have succeeded in getting a p2p session between a pair-o-pi’s on both stretch and buster. With stretch implementation and a systemd-networkd configuration (setup hints based on a couple postings by @Ingo) I was able to configure the system with the Group Owner (GO) running a DHCP server on the p2p interface (default is p2p-dev-wlan0) with and setting the client ip to a compatible address.

    Where I’ve run into my knowledge limits is with the native Buster networking setup. While I can get a p2p session up and running, the ip for the is the fallback for the pi (169.254.x.x), which requires manually assigning a compatible ip on the client’s p2p interface.
    If I understand the problem, I need a static address for my p2p-dev-wlan0 and a running DHPC server on the p2p interface to serve addresses to the connecting clients.
    Reading through your tutorial I’m thinking I need install and configure the isc-dhcp-server service. However, I don’t need a firewall, nor, I think an AP?
    Finally my Question: Is the dhcp server all I need? Or am I missing something else?
    Thanks for taking the time to read all of the above.
    FWIW: I don’t expect you to do the work! I’m just looking for some guidance in a realm where I have a serious knowledge deficit! 🙂

    BTW – as an avid off-road photographer, I find your setup pretty cool. NM is one of the states I love to visit for my timelapse nightscapes.

    Best,
    Bob Dunlop

    • Hey Bob! I’ll admit, some of those details are a bit opaque to me. To really give a concrete answer I’d need to sit down and reason through your design. However, generally speaking, I think you are correct. The isc-dhcp-server service should provide you with a subnet where you’re assigning IP addresses. If you trust the WAN connection or there is an upstream firewall, then no need for an extra firewall. If you already have a WiFi connection between the two Pis and that’s all you need, then I don’t see a need for an AP either.

      But, I guess the proof is in the pudding. Give it a shot and let me know how it goes! We can chat further via the email you dropped if you get stuck.

      Thanks re: the setup! We haven’t been in the van much lately due to quarantine, but I’m looking forward to getting back on the road. Actually, I’m just starting to play around with some time-lapses myself. I’m planning to do some time-lapse shots of my CNC/3D projects I’m building 🙂 You use a DSLR? A friend of mine, also a vandweller, built a Rapsberry Pi 3D skybox camera. He got some pretty great shots of deserts, grand canyon, etc.

      – Z

      • Hey Zane! Thanks for the quick reply.
        >If you trust the WAN connection
        With Wifi Direct there is no Wan! 🙂 Just the two (in my case) talking directly, no routing or DNS involved.
        Thanks for the feedback; I’ll pursue the isc-dhcp-server approach. Worst case scenario is more hours not spent doing something else, and reformatting another uSD card…
        Re: Timelapse – I’ve got some of my work up on Pond5:
        [link to www.pond5.com]
        Timelapse – Another way to spend hours doing something, and then more hours in front of a computer doing something more!

        Bob

  • Thanks for the nice write up. I’m having issues similar to Abhi; I can install/modify everything, but it only works with DHCPCD disabled. When I check to see if isc-dhcp-server is running, it is, but the Pi has no internet. I noticed that my WAN is missing when I run ifconfig, and the network indicator in the notification area of Raspberry Pi OS has a red x through it and it says that it has lost connection to dhcpd.

    • Heya, Adam. The missing WAN interface is very suspicious. The DHCP server should not be taking down the network interface. It’s acting as an intermediary, but that implies both interfaces must be present and active. I’d try reviewing syslogs journalctl -xe etc. to look for ifconfig (?) failing to bring up an interface. My hunch here would be something like a static IP block where you did not mean to place one, an ifconfig up/down script you forgot about or installed at some point… or something along those lines. Try to isolate exactly the moment when the WAN connection is lost and see what shows up in the logs at that time, as well as keep an eye on the status of the services (i.e., sudo systemctl status isc-dhcp-server). Hopefully that’ll produce some kind of useful information!

      • Thanks for responding so quickly, Zane! I’ve spent too much time on this project in the last few days, but I intend to pick it back up after I clear my mind a bit. The last two times I tried to set this up, I started from a clean Raspberry Pi OS Lite install and only followed the directions here. As far as I can tell, the exact moment WAN goes away is when I reboot after setting up isc-dhcp-server. Again, enabling DHCPCD makes everything work, but then my LAN gets assigned an address by whatever DHCP server is actually working, but when I run ifconfig it shows me the static IP that I set in the above instructions. That IP is pingable from other computers, but (in one of my early attempts) that address wasn’t available in a browser when looking for a Pi Hole installation.

        • Ah, something just jumped out at me. You mentioned “the LAN gets assigned an address by the DHCP server.” The LAN port should not be facing the upstream DHCP server, and should never have an IP address assigned; it is the internal interface which is not exposed to the internet. While re-reading Abhi’s post, I just noticed he had set the WAN to have a static IP, almost as if he had gotten LAN and WAN backwards. This seems like it would cause the exact symptoms you’re describing (notice how his code uses wan where my code block used lan, and I doubt the isc-dhcp-server could handle this case). So to review, the LAN should have a static IP and connect directly to the devices in your home (there should be no routers, etc. on the LAN port or anywhere on that side of the network at all). The WAN should have no modifications made to it (nothing in /etc/network/interfaces or /etc/network/interfaces.d/*, and connect to the internet (or another router), getting its IP address from DHCP (not static).

          • Hey Zane, just wanted to update. I think everything is fully working. The solution was to add /etc/network/interfaces.d/wan which included:

            auto wan
            alllow-hotplug wan
            iface wan inet dhcp

            The wan just refused to show up until I created this file. I took inspiration from Abhi’s situation, but instead just let wan be assigned an IP from my modem.

  • Hi Zane,

    Thanks for the excellent article! I am having a similar issue. Everything internal runs fine (i.e. the isc-dhcp service is handing out addresses with correct network parameters to lan based hosts). My issue is with the WAN. I believe my spectrum modem is in bridged mode as if I connect directly to my laptop it is assigned a public and reachable IP address.

    I’m a bit confused on what is responsible with obtaining the wan adapters address from the internet service. On reboot, wan does not show up via ifconfig and is not obtaining a lease. When I start dhcpcd (client dhcp) it immediately obtains a public facing address and I can connect to the internet fine from the raspberry pi itself (although routing from internal lan machines to the internet is still not working).

    Wondering if isc-dhcp-server is also supposed to be obtaining the wan lease?

    Thanks for any help!
    John

    • Hey John, I think I might have confused things when I split them out into three separate posts (router, network traffic, and DSL modem were originally one). But tl;dr, I think you’re right that in this setup, you need DHCP client and a bridge between the two interfaces. The confusion is that because I’m on DSL, pppoe handles this. If the interfaces are bridged and the dhcp client is up, does it work?

      Alternatively, people are reporting success with a single NIC in promiscuous mode. I want to look into this and see if I can standardize the approach, as it seems better.