Graywolf APRS iGate with RTL-SDR (Receive-Only)

A complete guide for running graywolf as a receive-only APRS iGate using an RTL-SDR dongle as the radio source, on Linux.

Graywolf is designed to consume audio from a real radio via a USB sound card. It does not natively accept stdin or SDR input. This guide bridges an RTL-SDR to graywolf using an ALSA loopback device — the SDR’s demodulated audio is piped to one end of a virtual sound card, and graywolf reads from the other end.

Tested on: Debian 13 (Trixie), x86_64, with an RTL-SDR Blog V3 dongle.

What You’ll End Up With

  • An always-on receive-only APRS iGate that survives reboots
  • Decoded APRS packets forwarded to APRS-IS under your callsign with -10 SSID
  • A web UI for monitoring and configuration
  • Two systemd services with proper dependency ordering, no manual intervention required

Prerequisites

  • A licensed amateur radio callsign (required to run an iGate)
  • An RTL-SDR dongle (V3 or similar)
  • A 2m antenna — even a poor one (e.g. an ADS-B antenna) will hear strong local stations
  • Linux box with at least one free USB port
  • rtl-sdr package and the SDR working with rtl_test
  • Internet connection for forwarding to APRS-IS
  • Root/sudo access

Step 1: Install ALSA Utilities

The ALSA tools provide aplay, which we use to feed audio into the loopback.

sudo apt install alsa-utils psmisc

psmisc provides fuser, useful for verifying which processes are using audio devices.

Step 2: Enable the ALSA Loopback Module

The snd-aloop kernel module creates a virtual sound card that pairs a “playback” device with a “capture” device — anything written to one appears as a recording on the other.

Load it now:

sudo modprobe snd-aloop

Make it persistent across reboots:

echo "snd-aloop" | sudo tee /etc/modules-load.d/snd-aloop.conf

Verify the loopback is present:

aplay -l | grep Loopback
arecord -l | grep Loopback

You should see Loopback listed in both. Note the card’s name (Loopback) — we use the name rather than the card number because card numbers can shift between reboots, but names are stable.

Step 3: Set a Recognizable Serial on the SDR (Optional but Recommended)

If you have multiple RTL-SDRs, set a unique serial on the one you’ll use for APRS so it’s easy to address by name.

List devices:

rtl_eeprom -d 0
rtl_eeprom -d 1

Set a memorable serial on your APRS dongle (e.g., 00144390 for the APRS frequency 144.390 MHz):

rtl_eeprom -d 0 -s 00144390

Confirm with y. Unplug and replug the SDR for the new serial to take effect, then verify:

rtl_eeprom -d 0

You can now reference this dongle by serial (-d 00144390) in commands, which is more reliable than relying on device index when you have multiple SDRs.

Step 4: Test the Audio Chain Manually

Before involving graywolf, verify rtl_fm can demodulate APRS audio and feed it into the loopback:

rtl_fm -d 00144390 -f 144.390M -s 44100 - | aplay -D plughw:Loopback,0,0 -r 44100 -f S16_LE -c 1 -t raw

Key flags:

  • -d 00144390 — your SDR’s serial (or use -d 0 for index)
  • -f 144.390M — North America APRS frequency. Use 144.800M in Europe, 144.640M in Australia.
  • -s 44100 — sample rate. 44100 Hz works reliably with plughw. Lower rates like 22050 are theoretically more efficient but graywolf’s UI may not list them.
  • plughw:Loopback,0,0 — the loopback’s playback side, addressed by name. The plughw plugin handles automatic format/rate negotiation, which avoids Channels count non available errors.
  • -c 1 — explicit mono. Required for aplay reliability.

Expected output should include lines like:

Tuned to 144643575 Hz.
Sampling at 1014300 S/s.
Output at 44100 Hz.

(The “Tuned to” frequency may not match exactly — that’s normal IF offset behavior in rtl_fm.)

If you have actual APRS activity in your area, you can verify decoding works by piping through multimon-ng instead:

sudo apt install multimon-ng
rtl_fm -d 00144390 -f 144.390M -s 22050 -g 42 - | multimon-ng -t raw -A -a AFSK1200 -

You should see decoded APRS packets appear after a minute or two. Press Ctrl-C when satisfied.

Step 5: Install Graywolf

Download the latest .deb from the graywolf releases page:

cd /tmp
wget https://github.com/chrissnell/graywolf/releases/latest/download/graywolf_linux_amd64.deb
sudo apt install ./graywolf_*.deb

(Filename may vary slightly. Check the releases page for the current naming.)

The installer creates a graywolf system user with audio and dialout group membership, installs binaries to /usr/bin/graywolf and /usr/bin/graywolf-modem, places config at /var/lib/graywolf/graywolf.db, and registers a systemd service.

Verify it started:

sudo systemctl status graywolf

The web UI should be available at http://localhost:8080 (or http://<your-ip>:8080 from another machine).

Step 6: Initial Graywolf Configuration

Open the web UI and set an admin password. The login is required before exposing the UI on a network.

sudo graywolf auth set-password --user admin -config /var/lib/graywolf/graywolf.db

Navigate to Station Callsign and enter your callsign without an SSID.

Step 7: Configure the Audio Device

In the web UI, go to Settings → Audio DevicesDetect Devices.

Among the detected devices you’ll see entries for Loopback. Select:

LoopbackRecommendedInputAlsaplughw:CARD=Loopback,DEV=1

Why DEV=1 and not DEV=0: in ALSA loopback, audio written to DEV=0 appears for capture at DEV=1. We pipe rtl_fm’s output to DEV=0 (playback side); graywolf reads from DEV=1 (capture side).

Why plughw: and not hw:: the plughw plugin handles automatic format/sample-rate/channel conversion. Raw hw: requires exact format matching, which is fragile.

In the device dialog:

  • Name: Loopback (or whatever)
  • Device Path: plughw:CARD=Loopback,DEV=1 (auto-filled)
  • Direction: Input
  • Source Type: Sound Card
  • Sample Rate: 44100 (must match what rtl_fm outputs)

Save.

Step 8: Configure the Radio Channel

Go to Settings → ChannelsAdd Channel:

  • Name: APRS-RX (or anything descriptive)
  • Modem Type: AFSK
  • Input Device: Loopback (the device you just added)
  • Input Channel: 0 (Left/Mono)
  • Output Device: None (RX only)
  • Bit Rate: 1200
  • Mark Freq (Hz): 1200
  • Space Freq (Hz): 2200

These are the standard APRS AFSK1200 parameters used worldwide.

Save.

Step 9: Configure the iGate

Go to Operations → iGate. The page has two tabs.

Connection tab:

  • Enable iGate: toggled on
  • APRS-IS Server: noam.aprs2.net (North America), euro.aprs2.net (Europe), aunz.aprs2.net (Oceania), or rotate.aprs2.net (auto-routing)
  • Port: 14580
  • Callsign: your callsign with -10 SSID (e.g., WB7ABC-10). The -10 SSID is the standard convention for receive-only iGates.
  • Passcode: generated from your callsign (without SSID). Use any APRS passcode calculator — it’s a 5-digit number derived deterministically from your callsign.

Save.

APRS-IS Feed & TX Rules tab:

  • APRS-IS Server Filter: r/<lat>/<lon>/100 — replace <lat> and <lon> with your station coordinates in decimal degrees, e.g. r/41.67/-111.87/100 for a 100 km radius. This tells the APRS-IS server which packets to send you for the live map.
  • TX Channel: leave at default. Since you have no IS→RF rules, this value is unused.
  • IS → RF Transmit Rules: leave empty. You’re receive-only, so no rules are needed. Adding any rule here would cause graywolf to attempt RF transmission, which an Rx-only station should never do.

Save.

Step 10: Restart Graywolf to Pick Up Configuration

sudo systemctl restart graywolf

While the manual rtl_fm | aplay command from Step 4 is still running in your terminal, the dashboard should now show:

  • RX Ready indicator green
  • Audio level around -10 to -3 dBFS
  • Packet count climbing as APRS activity is heard

Verify both processes are using the loopback:

sudo fuser -v /dev/snd/*

You should see two entries — aplay on pcmC<N>D0p (the playback side) and graywolf-modem on pcmC<N>D1c (the capture side). The card number <N> may vary.

Check the iGate connected successfully:

sudo journalctl -u graywolf | grep -i "aprs-is connected"

You should see a line like:

"aprs-is connected" component=igate server=rotate.aprs2.net:14580 callsign=WB7ABC-10

You can now confirm your station appears at https://aprs.fi/info/a/<YOUR-CALLSIGN>-10.

Stop the manual rtl_fm command (Ctrl-C in that terminal) — we’ll replace it with a systemd service next.

Step 11: Persistent rtl_fm via Systemd

Create a service that runs the rtl_fm pipe automatically and restarts on failure.

sudo tee /etc/systemd/system/rtl_fm-aprs.service > /dev/null <<'EOF'
[Unit]
Description=rtl_fm APRS audio bridge to ALSA loopback
After=sound.target network.target
Before=graywolf.service
Requires=sound.target

[Service]
Type=simple
ExecStartPre=/bin/sleep 5
ExecStart=/bin/bash -c '/usr/bin/rtl_fm -d 00144390 -f 144.390M -s 44100 - | /usr/bin/aplay -D plughw:Loopback,0,0 -r 44100 -f S16_LE -c 1 -t raw'
Restart=on-failure
RestartSec=10
User=root

[Install]
WantedBy=multi-user.target
EOF

Notes on the unit:

  • ExecStartPre=/bin/sleep 5 gives the system a moment to enumerate USB and load snd-aloop before rtl_fm tries to open the SDR
  • Restart=on-failure with RestartSec=10 means transient failures recover automatically
  • Before=graywolf.service works in concert with the override below to ensure graywolf starts after audio is flowing
  • Replace 00144390 with your SDR serial (or 0 for index)

Enable and start it:

sudo systemctl daemon-reload
sudo systemctl enable rtl_fm-aprs
sudo systemctl start rtl_fm-aprs
sudo systemctl status rtl_fm-aprs

You should see both rtl_fm and aplay listed in the cgroup tree under Tasks:.

Step 12: Make Graywolf Wait for Audio

Tell graywolf it depends on the rtl_fm service, so they start in the right order at boot.

sudo mkdir -p /etc/systemd/system/graywolf.service.d
sudo tee /etc/systemd/system/graywolf.service.d/override.conf > /dev/null <<'EOF'
[Unit]
After=rtl_fm-aprs.service sound.target
Requires=rtl_fm-aprs.service
EOF
sudo systemctl daemon-reload

This is functionally identical to sudo systemctl edit graywolf but doesn’t drop you into an interactive editor.

Step 13: Reboot Test

The moment of truth.

sudo reboot

After the system comes back up, verify:

sudo systemctl status rtl_fm-aprs graywolf
sudo fuser -v /dev/snd/*

Both services should be active (running), and fuser should show aplay and graywolf-modem on the loopback. The web UI should be accessible and packets should be flowing.

A single underrun!!! warning at startup is normal and harmless — it’s a sub-millisecond audio buffer hiccup that doesn’t affect packet decoding.

Verification & Troubleshooting

No packets being received

  • Confirm rtl_fm is tuning: sudo journalctl -u rtl_fm-aprs | grep "Tuned to" — should show ~144 MHz
  • Confirm graywolf-modem is reading from the loopback: sudo fuser -v /dev/snd/*
  • Test directly with multimon-ng to verify there’s actual APRS activity in your area
  • Try adjusting gain: change -g 49.6 (max gain) in the rtl_fm command if signals are weak
  • Try PPM correction: add -p <N> to rtl_fm if your SDR is off-frequency. Typical RTL-SDR PPM offsets range from -50 to +50.

aplay: set_params:1398: Channels count non available

You’re using hw: instead of plughw:. Switch to plughw:Loopback,0,0 in the rtl_fm-aprs service.

Card number changed after reboot

Use device names, not numbers. plughw:Loopback,0,0 is stable; plughw:2,0,0 is not.

iGate connects but iGated count stays at 0

The “Enable iGate” checkbox may not have saved. Re-toggle, save, restart graywolf:

sudo systemctl restart graywolf
sudo journalctl -u graywolf -n 50 | grep -i "aprs-is connected"

You should see a successful login line. If absent, the iGate component isn’t actually trying to connect.

rtl_fm service exits with status 1

Check what aplay is complaining about:

sudo journalctl -u rtl_fm-aprs -n 50

Most issues are resolved by switching hw: to plughw: and explicitly setting -c 1 for mono.

Two RTL-SDRs and the wrong one keeps getting selected

Set unique serials per Step 3 and reference them by serial in the service unit. Don’t rely on -d 0 / -d 1 — USB enumeration order can shift.

Operational Notes

Disk usage: graywolf’s SQLite databases (/var/lib/graywolf/graywolf.db and graywolf-history.db) grow over time but are typically modest — single-digit MB to a few tens of MB depending on local APRS activity.

CPU usage: graywolf-modem uses ~5–10% of a modern CPU core for AFSK1200 decoding at 44100 Hz; rtl_fm uses similar. The ALSA loopback adds negligible overhead.

Memory: Total footprint is ~50–80 MB resident across graywolf, graywolf-modem, rtl_fm, and aplay.

Updates: download a new .deb from the releases page and reinstall:

cd /tmp
wget https://github.com/chrissnell/graywolf/releases/latest/download/graywolf_linux_amd64.deb
sudo apt install ./graywolf_*.deb
sudo systemctl restart graywolf

The override file in /etc/systemd/system/graywolf.service.d/ is preserved across package upgrades since it’s outside the package’s managed paths.

Adding TX capability later: the easiest upgrade path is replacing the SDR with a real radio + USB sound card interface (Digirig, AIOC, etc.). Graywolf was designed for this and the configuration is more straightforward than the SDR loopback approach. Your existing graywolf install remains usable; you’d just add a new audio device, channel, and PTT configuration.

Why This Approach Works

Graywolf expects an ALSA capture device. RTL-SDR produces audio on stdout. The ALSA loopback module is the bridge — it presents two paired ALSA devices that act as a virtual cable. aplay writes to one end; graywolf reads from the other.

The plughw: plugin is the key piece that makes this robust. Raw hw: requires exact format/rate/channel agreement between writer and reader, which is brittle — plughw: performs automatic conversion, so as long as both sides specify something compatible with the loopback’s wide capability range, it works.

The systemd dependency chain (Requires= + Before= + After=) ensures clean startup order: kernel module loaded → SDR enumerated → rtl_fm-aprs running → graywolf starts and finds audio waiting.

This setup is, to my knowledge, not officially documented as a known-working configuration for graywolf. It’s been tested working on Debian 13 amd64. If you’ve replicated it on other distros or with other SDRs, consider contributing to the graywolf Known-Working Configurations page.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top