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

  1. 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.