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
-10SSID - 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-sdrpackage and the SDR working withrtl_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 0for index)-f 144.390M— North America APRS frequency. Use144.800Min Europe,144.640Min Australia.-s 44100— sample rate. 44100 Hz works reliably withplughw. 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. Theplughwplugin handles automatic format/rate negotiation, which avoidsChannels count non availableerrors.-c 1— explicit mono. Required foraplayreliability.
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 Devices → Detect Devices.
Among the detected devices you’ll see entries for Loopback. Select:
Loopback — Recommended — Input — Alsa —
plughw: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 → Channels → Add 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), orrotate.aprs2.net(auto-routing) - Port:
14580 - Callsign: your callsign with
-10SSID (e.g.,WB7ABC-10). The-10SSID 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/100for 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 Readyindicator 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 5gives the system a moment to enumerate USB and loadsnd-aloopbefore rtl_fm tries to open the SDRRestart=on-failurewithRestartSec=10means transient failures recover automaticallyBefore=graywolf.serviceworks in concert with the override below to ensure graywolf starts after audio is flowing- Replace
00144390with your SDR serial (or0for 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-ngto 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.