Building a multi-network ADS-B feeder with a $20 dongle
For a while now, I’ve wondered what to do with my old Raspberry Pi 3 Model B from 2017, which has basically been doing nothing ever since I replaced it with the Atomic Pi in 2019 and an old PC in 2022. I’ve considered building a stratum 1 NTP server, but ultimately did it with a serial port on my server instead.
Recently, I’ve discovered a new and interesting use for a Raspberry Pi—using it to receive Automatic Dependent Surveillance–Broadcast (ADS-B) signals. These signals are used by planes to broadcast positions and information about themselves, and are what websites like Flightradar24 and FlightAware use to track planes. In fact, these websites rely on volunteers around the world running ADS-B receivers and feeding the data to them to track planes worldwide.
Since I love running public services (e.g. mirror.quantum5.ca), I thought I might run one of these receivers myself and feed the data to anyone who wants it. I quickly looked at the requirements for Flightradar24 and found it wasn’t even that much—all you need was a Raspberry Pi, a view of the sky, and a cheap DVB-T TV tuner, such as the very cheap and popular RTL2832U/R820T dongle, which has a software-defined radio (SDR) that could be used to receive ADS-B signals.
I have enough open sky out of my window to run a stratum 1 NTP server with a GPS receiver, so I figured that was also sufficient for ADS-B1. Since I found an RTL2832U/R820T combo unit with an antenna for around US$20 on AliExpress, I just decided on a whim to buy one. Today, it arrived, and I set out to build my own ADS-B receiver.
Choice of software
Once I had the dongle, I decided to do some research on what software I should
install to feed to as many networks as possible, since I didn’t want to limit
myself to just one. As it turns out, every network appears to have its own
software, conveniently packaged into its own OS image, or has random shell
scripts that you are expected to curl
and run which will “magically” set
everything up. Even though the ones with scripts all claim to play nicely with
each other, there’s something deeply disturbing about setting it up this way.
So I took a look at what these scripts were actually doing, and it turned out,
they were all just running a variant of dump1090
(for 1090 MHz ADS-B
signals), either dump1090-mutability
or FlightAware’s
dump1090-fa
, but they are all compatible with the various feeder
software. In theory, the various feeders should all be able to share the same
instance of dump1090
, and sometimes the scripts do attempt to do so, but the
active instance ultimately belongs to one of the feeders, resulting in a tangled
mess of dependencies. Instead, I decided to install dump1090
myself and
configure all the feeders to use it.
There is also dump978
for 978 MHz ADS-B signals, but since those aren’t
used in Canada, I didn’t bother setting it up. If you live in the US, setting up
dump978
is left as an exercise for the reader.
Setting up dump1090
I don’t like using software outside official repositories if I can avoid it, so
I decided to go for dump1090-mutability
, which is part of Debian’s official
repositories. Installing it was trivial:
$ sudo apt install --no-install-recommends dump1090-mutability
...
The following NEW packages will be installed:
dump1090-mutability libjs-excanvas libjs-jquery-ui libjs-jquery-ui-theme-smoothness librtlsdr0
...
Note that I passed --no-install-recommends
because dump1090-mutability
recommends lighttpd
, which it configures to display a local map of all the
planes it detects. If you like lighttpd
and don’t have an existing web server,
then feel free to omit it. I wanted to use nginx
though.
At this point, I tried to start the daemon, but it didn’t work:
$ sudo systemctl start dump1090-mutability.service
$ tail /var/log/dump1090-mutability.log
Sat Apr 5 21:18:18 2025 UTC EB_SOURCE EB_VERSION starting up.
Using sample converter: UC8, integer/table path
Found 1 device(s):
0: unable to read device details
usb_open error -3
Please fix the device permissions, e.g. by installing the udev rules file rtl-sdr.rules
Error opening the RTLSDR device: Permission denied
A quick examination revealed that dump1090-mutability
runs under the
dump1090
user, and that user needs access to the device files. As suggested,
we should create rtl-sdr.rules
. Unfortunately, there’s no link and there are
multiple variants of the file, but this one worked (mirror):
$ sudo wget -O /etc/udev/rules.d/rtl-sdr.rules https://raw.githubusercontent.com/osmocom/rtl-sdr/refs/heads/master/rtl-sdr.rules
You can now unplug and replug the dongle to update the permissions on the device files, or simply run:
$ sudo udevadm trigger
However, these udev
rules only granted access to the plugdev
group, so we’ll
need to ensure dump1090
is a member of that group:
$ sudo adduser dump1090 plugdev
Adding user `dump1090' to group `plugdev' ...
Done.
Now, it should start cleanly:
$ sudo systemctl restart dump1090-mutability.service
$ tail /var/log/dump1090-mutability.log
Sat Apr 5 21:27:01 2025 UTC EB_SOURCE EB_VERSION starting up.
Using sample converter: UC8, integer/table path
Found 1 device(s):
0: Realtek, RTL2838UHIDIR, SN: 00000001 (currently selected)
Detached kernel driver
Found Rafael Micro R820T tuner
Max available gain is: 49.60 dB
Setting gain to: 49.60 dB
Gain reported by device: 49.60 dB
Allocating 15 zero-copy buffers
If it doesn’t work for you, the log will hopefully tell you why.
Isolation
There’s something super yucky about setting stuff up with random bash
scripts,
especially on this Raspberry Pi that took some significant effort to get
Debian working, which meant that if it broke the system, I’d waste a large
amount of time fixing it. So instead, I opted to run all the feeders in a
systemd-nspawn
container. Now theoretically, I could run this container
anywhere since it’d be talking to dump1090
over TCP, but I thought it’d be fun
to run it on the Raspberry Pi itself. If you are interested in setting it up
this way, feel free to consult my post on systemd-nspawn
.
For the rest of the post, we’ll assume that the Raspberry Pi 3 is at 192.0.2.1
and the ADS-B feeders run on the container at 192.0.2.2
. To avoid massive pain
with USB passthrough, dump1090
should be run on real hardware (i.e. the Pi).
This will require dump1090
and the various feeders to be configured to use the
correct IPs, since they all default to localhost
, i.e. assuming it’s running
on the same device.
Of course, you could just run the feeders on the Raspberry Pi directly if you so wish, saving all the trouble, at the risk of the scripts doing something crazy to your system.
Setting up dump1090
web UI
Before feeding your data to the various networks, you should probably check that
it works locally. dump1090
comes with a web UI. If you used the lighttpd
thing that it installs by default, then it should just work at
http://192.0.2.1/dump1090/gmap.html
.
You probably want to change the default starting location of the map to where
you live, or you’d have to move the map around to your location every time. To
do this, edit /etc/dump1090-mutability/config.js
and change DefaultCenterLat
and DefaultCenterLon
to your position. You may also want to change SiteShow
to true
and SiteLat
and SiteLon
to your position so you can see where the
planes are relative to you.
Since I wanted to use nginx
, I installed it on the Raspberry Pi and wrote the
following configuration:
server {
listen 80;
server_name adsb.example.com;
root /usr/share/dump1090-mutability/html;
index gmap.html;
location /data/ {
alias /run/dump1090-mutability/;
}
}
Now, you should be able to see your feed at http://adsb.example.com
once the
DNS is pointed correctly. Remember to change the domain! Setting up HTTPS is
left as an exercise for the reader.
You should see a map with a list of aircraft and the current time on the side. For privacy reasons, I will not share a screenshot.
Flightradar24
The first network I fed my data to was flightradar24
. They have an install
script which installs their repository and their feeder daemon, fr24feed
. It’s
as simple as:
$ wget -qO- https://fr24.com/install.sh | sudo bash -s
...
Welcome to the FR24 Decoder/Feeder sign up wizard!
Before you continue please make sure that:
1 - Your ADS-B receiver is connected to this computer or is accessible over network
2 - You know your antenna's latitude/longitude up to 4 decimal points and the altitude in feet
3 - You have a working email address that will be used to contact you
4 - fr24feed service is stopped. If not, please run: sudo systemctl stop fr24feed
To terminate - press Ctrl+C at any point
Step 1.1 - Enter your email address ([email protected])
$:[redacted]
Step 1.2 - If you used to feed FR24 with ADS-B data before, enter your sharing key.
If you don't remember your sharing key, you can find it in your account on the website under "My data sharing".
https://www.flightradar24.com/account/data-sharing
Enter your sharing key or press ENTER/RETURN to continue.
$:
Since I’ve never fed data before, I left this field blank. They generated a new key for me and emailed it to me. If I were setting it up again, I’d enter the previous key.
Step 1.3 - Would you like to participate in MLAT calculations? (yes/no)$:no
Since they explicitly ask you to not enable MLAT when sharing with other networks on their website, I said no. It’s still not clear to me what the problem is, since a few other networks are happily using my MLAT data.
Step 4.1 - Receiver selection:
1 - DVBT Stick (USB)
-----------------------------------------------------
2 - SBS1/SBS1er (USB/Network)
3 - SBS3 (USB/Network)
4 - ModeS Beast (USB/Network)
5 - AVR Compatible (DVBT over network, etc)
6 - microADSB (USB/Network)
Enter your receiver type (1-6)$:5
Step 4.2 - Please select connection type:
1 - Network connection
2 - USB directly to this computer
Enter your connection type (1-2)$:1
Step 4.3A - Please enter your receiver's IP address/hostname
$:192.0.2.1
Step 4.3B - Please enter your receiver's data port number
$:30002
Step 5.1 - Would you like to enable RAW data feed on port 30334 (yes/*no*)$:no
Step 5.2 - Would you like to enable Basestation data feed on port 30003 (yes/no)$:no
Saving settings to /etc/fr24feed.ini...OK
Settings saved, please restart the application by running the command(s) below to use new configuration!
sudo systemctl restart fr24feed
I opted to use DVB-T over network, since that’s my setup. If you are running it
locally, I think you can select the network option and just enter localhost
for the hostname. This should allow you to use your own instance of dump1090
as opposed to having fr24feed
run its own.
Now I just restarted the daemon:
$ sudo systemctl restart fr24feed
And soon, my radar showed up on “my data sharing” on Flightradar24 and I was able to filter for planes seen by my receiver.
If it doesn’t work, check the logs with sudo journalctl -u fr24feed
.
FlightAware
FlightAware has their own feeder called piaware
, which you can install from
their APT repository. For up-to-date instructions, see here,
but this was what I ran:
$ wget https://www.flightaware.com/adsb/piaware/files/packages/pool/piaware/f/flightaware-apt-repository/flightaware-apt-repository_1.2_all.deb
...
$ sudo dpkg -i flightaware-apt-repository_1.2_all.deb
...
flightaware-apt-repository: regenerated APT configuration /etc/apt/sources.list.d/flightaware-apt-repository.list
flightaware-apt-repository: please run 'sudo apt update' to use the new configuration
$ sudo apt update
...
$ sudo apt install piaware
The following NEW packages will be installed:
itcl3 libboost-program-options1.74.0 libboost-regex1.74.0 libestr0 libfastjson4 liblognorm5 libtcl8.6 piaware rsyslog tcl tcl-tls tcl8.6 tcllib tclx8.4
...
At this point, if you are running piaware
directly on the Raspberry Pi, things
should just work. However, since I chose to run it in a container, I had to do
some extra configuration:
$ sudo piaware-config receiver-type other
Set receiver-type to other in /etc/piaware.conf:7
$ sudo piaware-config receiver-host 192.0.2.1
Set receiver-host to 192.0.2.1 in /etc/piaware.conf:8
$ sudo piaware-config mlat-results-format 'beast,connect,192.0.2.1:30104 beast,listen,30105 ext_basestation,listen,30106'
Set mlat-results-format to beast,connect,192.0.2.1:30104 beast,listen,30105 ext_basestation,listen,30106 in /etc/piaware.conf:9
$ sudo systemctl restart piaware.service
If it’s not working, check sudo journalctl -u piaware
.
ADS-B Exchange
Next, I tried feeding my data to ADS-B Exchange. They have their shell
script which did some irritating things, like installing a compiler to build
their own MLAT code, and shoved a bunch of Git repos under
/usr/local/share/adsbexchange
.
It also required pkg-config
without first installing it. This is a transcript
of the correct installation process:
$ sudo apt install pkgconf
...
The following NEW packages will be installed:
libpkgconf3 pkgconf pkgconf-bin
...
$ curl -L -o /tmp/axfeed.sh https://www.adsbexchange.com/feed.sh
$ sudo bash /tmp/axfeed.sh
...
The following NEW packages will be installed:
binutils binutils-aarch64-linux-gnu binutils-common build-essential bzip2 cpp cpp-12 dpkg-dev g++ g++-12 gcc gcc-12 libasan8 libatomic1 libbinutils libc-dev-bin libc6-dev libcc1-0 libcrypt-dev libctf-nobfd0 libctf0 libdpkg-perl libexpat1-dev libgcc-12-dev libgomp1
libgprofng0 libhwasan0 libisl23 libitm1 libjs-jquery libjs-sphinxdoc libjs-underscore liblsan0 libmpc3 libmpfr6 libncurses-dev libncurses6 libnsl-dev libpython3-dev libpython3.11 libpython3.11-dev libstdc++-12-dev libtirpc-dev libtsan2 libubsan1 libzstd-dev
linux-libc-dev make patch python3-dev python3-distutils python3-lib2to3 python3-pip-whl python3-setuptools-whl python3-venv python3.11-dev python3.11-venv rpcsvc-proto socat uuid-runtime zlib1g-dev
...
Installing mlat-client to virtual environment
...
Compiling / installing the readsb based feed client
...
It then showed a wizard in the console, asking for information like GPS coordinates. Once that’s done, things should work if you are running it on the Raspberry Pi directly.
Since I didn’t, I instead got this:
---------------------
No data available from IP 127.0.0.1 on port 30005!
---------------------
If your data source is another device / receiver, see the advice here:
https://github.com/adsbexchange/wiki/wiki/Datasource-other-device
I had to edit /etc/default/adsbexchange
, making the following changes:
INPUT="192.0.2.1:30005"
RESULTS="--results beast,connect,192.0.2.1:30104"
And then sudo systemctl restart adsbexchange-{feed,mlat}.service
.
Once that’s done, you should be able to see your feed on ADS-B Exchange’s feeder status if you visit it from the same IP as your feeder.
If it doesn’t work for some reason, look at
sudo journalctl -u adsbexchange-feed
for ADS-B feed issues and
sudo journalctl -u adsbexchange-mlat
for MLAT issues.
There is also a stats daemon to help them display the planes received by you, which you can install like this:
$ curl -L -o /tmp/axstats.sh https://www.adsbexchange.com/stats.sh
$ sudo bash /tmp/axstats.sh
...
Cloning into '/tmp/adsbexchange-stats-git'...
...
The following NEW packages will be installed:
bash-builtins bind9-host bind9-libs jq libfstrm0 libjemalloc2 libjq1 liblmdb0 libmaxminddb0 libonig5 libprotobuf-c1 libuv1
...
Additional information should now be available on the feeder status page.
adsb.fi
adsb.fi is basically a clone of ADS-B Exchange that was created due
to some drama over ADS-B Exchange being sold. The installation
process is basically the same, except you use the shell script from
https://adsb.fi/feed.sh
, edit /etc/default/adsbfi
, the systemd units are
adsbfi-feed.service
and adsbfi-mlat.service
, and there is no stats daemon.
For the latest instructions, see here.
OpenSky
OpenSky is another network, though more focused on research than aviation enthusiasts like the others. They have an APT repository, but it was broken at the time of writing. So instead, I installed the latest deb:
$ wget https://opensky-network.org/files/firmware/opensky-feeder_latest_arm64.deb
...
$ sudo apt install ./opensky-feeder_latest_arm64.deb
Unpacking opensky-feeder (2.1.7-1) ...
Setting up opensky-feeder (2.1.7-1) ...
At this point, it pops up a wizard asking you for location and dump1090
connection information. I simply filled it in and it was installed and started.
Once again, if you run into issues, look at sudo journalctl -u opensky-feeder
.
Monitoring with Prometheus
I thought it’d be fun to monitor it with the Prometheus instance I set up
recently, so I installed dump1090-exporter
on the Raspberry Pi (it should go
on the same machine as dump1090
):
$ sudo apt install python3-venv python3-dev
...
$ python3 -m venv /opt/dump1090-exporter
$ /opt/dump1090-exporter/bin/pip install dump1090exporter aiohttp==3.10.10
Note that I had to override a newer version of aiohttp==3.10.10
since the old
version didn’t compile on Python 3.11, which shipped with Debian bookworm
.
And then it’s just a matter of creating a new systemd unit
/etc/systemd/system/prometheus-dump1090-exporter.service
:
[Unit]
Description=Prometheus exporter for dump1090, an ADS-B receiver
[Service]
Restart=on-failure
DynamicUser=true
ExecStart=/opt/dump1090-exporter/bin/dump1090exporter --resource-path=/run/dump1090-mutability --port=9105 --latitude=[redacted] --longitude=[redacted]
[Install]
WantedBy=multi-user.target
Then it was just a matter of
sudo systemctl enable --now prometheus-dump1090-exporter.service
and telling
Prometheus to pull logs from http://192.0.2.1:9105/metrics
.
Conclusion
This was a fun weekend project. Setting up an ADS-B receiver was surprisingly easy, even when doing it in a network-agnostic way. I was quite impressed with what a $20 TV tuner managed to accomplish.
My only regret is not having enough open sky, resulting in a limited field of view.
Notes
-
As it turns out, for the stratum 1 NTP server, it didn’t really matter as long as you could see enough GPS satellites. However, for ADS-B, the more sky you can see, the more planes you can track. So ideally, if you can, you should mount the antenna on the roof of a relatively tall building. It’s probably fine to feed it as long as you have a reasonable view of a quadrant of the sky, but it might not be worth it if your view is almost completely blocked by another building. ↩