Raspberry Pi Zero as a router-attached ethernet gadget

Let's say you want to run a non-OpenWRT application such Adguard Home or Pi-Hole as an add-on to your router.

But on the one hand you don't want to dedicate another system to it, using up a switch port and ethernet cabling and yet another wall wart.

On the other hand you don't want to or can't install it on your router because of its limited storage, memory and CPU. To say nothing of the fact that it'll get erased when you upgrade OpenWRT.

Enter the Raspberry Pi Zero ethernet gadget. The Zero is a $5 computer less than half the size of a business card, and properly configured it can be plugged directly into your router's USB port and act as an ethernet-connected computer, implementing both the host and the client interface. The power provided by the USB host is usually sufficient and it won't need its own power supply.

What that means is when you turn on your router, on comes a second, extremely efficient Linux computer with 512MB of RAM and as much storage a you're willing to buy in the form of a MicroSD card. (Last time I looked the sweet spot was 32GB in a quality brand like SanDisk for well under $10.)

Instructions for getting AdGuard Home up and running on a Pi Zero attached to a USB-equipped OpenWRT router


This is mostly specific to this application, of configuring the Pi Zero as an ethernet gadget, and your router to support and connect to it. Detailed instructions on the various ways of using AdGuard can be found, for example, here:

Other subjects such as how to write an OS to an SD card are well-documented elsewhere.

Notes and Warnings

Difficulty: I've tried to be thorough enough that beginner can follow this, but this is really fairly ADVANCED, and uses a feature which is considered experimental. Among other things it proposes configuring a headless system for network-only access, without ever attaching a keyboard or display. If you're not comfortable working with the Linux shell, modifying your network and editing configuration files then this can be an adventure, and you'll want to play around on another computer (another Pi such as the 3 or 4 B makes a good host for experimenting) rather than commit your router to this and possibly break your network.

Suitability for production use: I did this for fun and have not used it long-term. It works well with all three routers I tried, but that's a tiny sample, and only one of them is a retail embedded router, a Linksys EA6350 v3; the others were a Pi 4 and an x86_64 machine.

  • Your router might not supply sufficient stable power (which is still okay if you hook up a wall wart to the power port). All the ones I tested did, but again, only one was a retail embedded router.
  • The ethernet gadget mode of the Zero is an experimental or "hobbyist" feature, not commonly used in production nor considered critical by maintainers, it is possible that it might not work reliably on your router's platform.

About the new Pi Zero 2 W: I haven't tried it. It is expected to support ethernet gadget mode, but the more powerful CPU will certainly need a little more current and is less likely to be happy with what a router's USB port puts out.

Configuring the Pi Zero to be an ethernet gadget

There are basically two ways to configure a Pi Zero. The standard way is to hook up a keyboard and monitor to it, boot it from a newly-written MicroSD card and configure it directly. The problem with this is it requires an OTG USB adapter or hub and a mini-HDMI cable. If you already have those things, great, do it that way. But here we're going to configure a Micro SD card on another machine, then slip it into the Pi Zero, plug that into a router's USB port and just expect everything to work, because we're clever and brave. To follow these instructions, you'll need the ability to mount a MicroSD card on a Linux machine. If nothing else your OpenWRT router will serve this purpose, if you have a USB to [SD to] MicroSD adapter.

Download the latest Raspbios image -- I recommend the Lite edition -- for the Pi Zero, unzip it and write it to a MicroSD card. Instructions at the Raspberry Pi site and all over the web.

Next, connect the card via a reader or adapter to the running Linux system you're going to use to configure it, then type:

dmesg | tail

to see what device it was allocated in the /dev/sd? hierarchy. I'm just going to say /dev/sdX here, substitute the correct one. Now mount the first partition:

mount /dev/sdX1 /mnt

Create an empty file called "ssh"; this tells a Pi at boot to start an ssh daemon:

touch /mnt/ssh

To the file /mnt/config.txt, add a line consisting of "dtoverlay=dwc2"

echo -e "\ndtoverlay=dwc2" >> /mnt/config.txt

(The first newline is usually a good idea in case the file doesn't already end with one.)

Now umount the first partition and mount the second.

umount /mnt
mount /dev/sdX2 /mnt

Add dwc2 and g_ether to /mnt/etc/modules.

echo -e "\ndwc2\ng_ether" >> /mnt/etc/modules

Now, invent a pair of mac addresses. They'll each consist of six 8-bit (i.e. two digit) hex numbers separated by colons. One of them will become the hardware address of your new USB ethernet interface on the router (cleverly provided by the Pi Zero), the other will become the mac also used by the Pi Zero as the hardware address of its client interface. The ones shown below are just examples, come up with your own. I'll use these:

00:6d:eb:a8:44:7f (host)
1a:0d:c4:d1:19:9b (client)

Create the file /mnt/etc/modprobe.d/g_ether.conf with the following line:

options g_ether host_addr=00:6d:eb:a8:44:7f dev_addr=1a:0d:c4:d1:19:9b

For example (and using your own choice of mac addresses):

echo "options g_ether host_addr=00:6d:eb:a8:44:7f dev_addr=1a:0d:c4:d1:19:9b" > /mnt/etc/modprobe.d/g_ether.conf

Decide what IP network you want the new dedicated interface on your router to have. Let's say it's

Using an editor like vi or nano, or just cat if you're familiar with it, create a file /mnt/etc/network/interfaces.d/usb0. You're going to give the Pi Zero a static address. If your router will be at on this interface, and you want the Pi Zero to have the address, it'll consist of the following lines:

allow-hotplug usb0
iface usb0 inet static


Unmount the SD card again:

umount /mnt

You're done and can remove it.

Configuring the router

In an OpenWRT ssh session:

opkg update
opkg install kmod-usb-net-cdc-ether

Okay we're nearly there. With the Pi Zero UNPOWERED and DISCONNECTED (yes, you can physically damage, not just destroy data on, an SD card by inserting/removing it from a powered Pi, I've done it), insert the Micro SD card into the Pi Zero, and connect it via a standard USB A to micro-B cable to the router using the MIDDLE port on the Zero, the one near the mini-HDMI port, not the one at the end. This port can take both power and data; the one at the end only takes power.

Waich for a green LED on the Zero, and wait about one minute while it goes through its first boot, which does several first-boot tasks automatically. Then, still on the router, type

dmesg | tail

Two of the latest lines should look something like this (substituting your own host_addr value):

[  2048.489085] cdc_ether 1-1:1.0 eth1: register 'cdc_ether' at usb-xhci-hcd.0.auto-1, CDC Ethernet Device, 00:6d:eb:a8:44:7f
[  2048.489403] usbcore: registered new interface driver cdc_ether

Okay, this indicates you have a new ethernet device on your router. It will be ethN, where N is the highest-numbered ethernet device now present.

In Luci, add a network interface.

Name: (anything you like. Call it "zero", for example)
Static address:
Device: (select the new interface from the drop list.)
Firewall zone: same as your lan, usually "LAN". Make sure forward policy for this zone is allow, as this controls whether traffic can move between interfaces within the zone.

Save and apply all your changes.

NOTE: If you currently have any firewall rules hijacking or preventing lan devices from using their own public DNS servers, now would be a good time to enter an exception for It's going to need to be able to access public dns servers.

Now back at the command line, you should be able to ping the Pi Zero:


If you see ping results you're golden. Try ssh (again, using your own chosen network/IP):

ssh -l pi

Default password will be raspberry. A notice will be displayed encouraging you to change it. You should do so at some point using the passwd command.

Become root

sudo -s

Temporarily get yourself some DNS service from the router (again, substitute your router's address on the new network):

echo "nameserver" >> /etc/resolv.conf

Test it

ping download.openwrt.org

Assuming everything's fine and that worked, (first place to look if it didn't is at your firewall policies and rules),

Probably not a bad idea to upgrade it to get all the patches:

apt-get update
apt-get dist-upgrade

Wait for that to complete without error, then


You'll be kicked off the ssh session and back to OpenWRT on the router. Use dmesg | tail to see when the interface comes back. In most cases it will be restored and you'll be able to ssh in again, but I've seen one case where it did not and you have to reboot the router whenever you reboot the Zero if you want this device to reinitialize. If that's the case, it's up to you whether you want to A) accept the situation, B) persue a solution, C) give up, D) use a different router, or whatever.

If it's back, ssh back into the Zero.

ssh -l pi
sudo -s

Again, get temporary name resolution

echo "nameserver" >> /etc/resolv.conf

Now we're going to install AdGuard Home:

cd /usr/local

wget https://github.com/AdguardTeam/AdGuardHome/releases/download/v0.107.0-b.14/AdGuardHome_linux_armv6.tar.gz

(change this to a newer version if available)

tar zxvf AdGuardHome_linux_armv6.tar.gz

Assuming it all unzipped just fine, you can get rid of the tar file if you want:

rm AdGuardHome_linux_armv6.tar.gz

Now install it

AdGuardHome/AdGuardHome -s "install"

Now AdGuard will start on the Zero at boot time. Make sure it's running now:

service AdGuard start

By default AdGuard's web interface listens on port 3000 and its DNS interface listens on port 5353.

On the Zero's ssh session, edit /usr/local/AdGuardHome/AdGuardHome.yaml

Change the first bind_port entry to 80
Under DNS, change the port entry to 53

Save and restart AdGuardHome. You can now configure it via (or whatever IP net/address you chose). See other sources for how to do that. One specific thing you'll want to do for this use case is add your OpenWRT router as the resolver for your local domain, and also for plain hostnames with no domain. At the end of the list of upstream DNS servers:


Assuming "/lan/" and "lan" are the values you have for your local domain in OpenWRT's local domain fields in the general dhcp/dns section. If you use your own domain, substitute that.

Test that this can resolve before proceeding further

ping forum.openwrt.org

Also try manually setting a laptop or something to use (or whatever) as its DNS server, and start visiting sites you haven't visited recently. If this doesn't work, it's time to go back and figure out what we missed. It's probably your firewall policies.

Set AdGuard Home to be the main DNS server for the network

In OpenWRT, modify /etc/config/dhcp

In the section "config dnsmasq"

list dhcp_option '6,'

If you have any upstream resolvers in OpenWRT ("list server") you can remove them, but you might want to wait: it doesn't matter much because they're not going to get used except when the router itself is accessing the internet, and we don't ever do that except when updating our packages, right? It's up to you whether to add the Pi Zero as your sole upstream here. In theory that can create a circular delegation, but since the Pi should never delegate lookups to the router for anything but the local domain, in practice something would have to be pretty broken for that problem to occur. (Nevertheless I have not done it; my router uses as its upstream for its own use, to eliminate a dependency on the Pi Zero being running when I'm doing router maintenance.)

If you've left your upstream routers defined in OpenWRT, any client machines on your netwrok that have not yet acquired the new DNS address will still use the old one successfully. Once the client systems have the new IP address -- they should get it the next time they make a DHCP query -- they should start using it.

If you try this and have issues (probably because I missed something working from memory, or maybe your firewall is sufficiently different from mine that something isn't getting through), I'll look into this forum now and then and help out if I can but I may not be immediately available. Chances are someone can help, though.


Very helpful, I can ping the pi from openwrt but when I try to log in to if using ssh 192.168.x.xxx -l pi my password is not accepted. If I plug the pi into a usb port on a pc I can ssh into it as pi with the usual password. Any ideas?

Try ssh -vvv ... (make ssh really verbose) and see where it's getting hung up. Might be a policy issue, make sure that INPUT and OUTPUT are accepted on that interface.

Edit: just want to be sure that you're certain that the problem is it's not accepting the password when connected to the router, but it accepts it when connected to a PC. Because that's a real brain-bender; whether a host accepts an ssh password is purely up to that host and it's difficult to hypothesize a probable, real-world reason it would work differently based on the connecting client.

Quick note for anyone using AdGuard Home as their primary resolver, and counting on it forwarding requests to the local domain to OpenWRT's dnsmasq:

Not all clients will add the local domain to the request for a naked hostname, even if dnsmasq's DHCP passes it to the host as the search domain. It is up to the application to do this, not the host, and some applications don't. Test this if you like with "dig" (which doesn't) and nslookup (which does, at least on MacOS)

So if you've got this in your AdGuard Home list of resolvers:


(Where "myfabulousdomain.tld" is whatever you've in your local domain settings for DNS/DHCP in OpenWRT, is your OpenWRT router's IP, and (optionally -- this is not necessary if they're on different hosts) :5353 points it to the port you've got OpenWRT's dnsmasq listening on)

This will only work for appliances and applications which append the local domain to requests for undecorated hostnames, either automatically or when the hostname alone returned NXDOMAIN. Some don't. Notably, a lot of IoT devices don't.

The answer is to add this to your list of resolvers in AdGuardHome:


Or whatever. For example, my setup is

[/mydoghasfleas.org/]  <-- my openwrt router

Well, not really; I made up that domain name and IP. The point is the pattern in that last one -- which as far as I can tell is undocumented -- apparently tells AdGuard Home to delegate undecorated hostnames (i.e. no domain at all) to the IP address[:port] indicated. Hope this helps someone.

zero2w works like a charm!! My only 'problem' was that if macbook went to sleep the usb0 wasnt coming back after waking up. Maybe I need a script to restart things.

I now got a gpio tft from adafruit so I can activate scripts when headless but Im still trying to make the tft work (with the python libs, not kernel framebuffer)

Thank you for sharing the detailed write up!

I am just curious to know how is it different from installing AdGuard on RPi separately without making it as a router attached ethernet gadget.

The original pi zero only has usb, no ethernet or wifi, that is how it is different from the link you have provided.

Makes sense! I have pi zero W which has wifi capability so I could go with alternate approach.


This is more than an experiment for me now, I've put it into production. However, raspios is a full systemd-based OS: it does arbitrary disk writes, requires a clean shutdown and is rather bloated for appliance use. I decided to put an appliance OS on it instead. My choices were Alpine and OpenWRT. Decided to try OpenWRT as it's much more lean and compact. If you do this, you'll want to disable dnsmasq, odhcp, firewall and wpad as they'll just be in the way.

Getting a functioning OpenWRT install with all necessary drivers on a non-wifi Zero without doing a custom build is pretty labor intensive; it probably took me 1-2 hours and required a keyboard, monitor, mini-HDMI cable, a USB OTG hub and a USB2-compatible ethernet dongle. But that's just for setup, you don't need any of those things once it's configured.

There are slight syntactic differences between OpenWRT and raspios in setting up ethernet gadget mode. Details if anyone wants to try it. It's working perfectly in production now in my 1G fiber multi-VLAN, multi-telework household.

(Edit: the ethernet dongle wasn't really needed for this install, it was just of a convenience to allow me to complete the setup over the network rather than from the keyboard and monitor. This approach will work without it.)

1 Like