Overview
The MatchX MX190x (M2 Pro) is a LoRaWAN gateway originally designed for the MXC/MatchX supernode network. However, the MatchX provisioning server (ps-glo.matchx.io) is no longer operational and MatchX support is unresponsive. This guide documents how to configure the MX190x to work as a standard LoRaWAN packet forwarder with ChirpStack, bypassing the MXC infrastructure entirely.
All configuration is performed via the gateway’s web API (Express.js backend on port 80). No root shell or SSH access is required.
Key Challenges & Solutions
| Challenge | Solution |
|---|---|
ENOENT error – /data/global_conf.json missing on fresh units |
POST to /api/loraConfig creates the file and directory |
| Packet forwarder crashes after ~60 seconds | DNS fix: resolve ps-glo.matchx.io to 127.0.0.1 via local dnsmasq |
| Web UI password is not the Linux root password | Use the web API exclusively; root console access is not needed |
| Factory reset does not clear the root console password | The root password is firmware-baked; the web API provides full configuration capability |
Prerequisites
- MatchX MX190x gateway (MX1901 for EU868 or MX1902 for other regions) with power supply
- Ethernet connection to local network with DHCP
- ChirpStack v4 network server with Gateway Bridge listening on UDP port 1700
- A Linux server on the same network to run dnsmasq (for the DNS fix)
- Gateway ID pre-registered in ChirpStack for each gateway
- USB-C cable and Silicon Labs CP2104 driver (optional, for serial console debugging)
Step 1: Physical Connection & Network Access
Network Connection
Connect the gateway via Ethernet. It will obtain an IP address via DHCP. The gateway also creates a WiFi access point:
| Setting | Value |
|---|---|
| WiFi SSID | MatchX_MX190x_<last 4 chars of serial> |
| WiFi Password | Device serial number (printed on the label, format: M2X...) |
| WiFi Gateway IP | 192.168.2.2 |
Check your DHCP server or router for the gateway’s Ethernet IP address. You can also connect to the WiFi AP and access the gateway at 192.168.2.2. The mDNS hostname is <serial>.local.
Serial Console (Optional)
For debugging, connect via USB-C using a serial terminal at 115200 baud, 8N1. The USB-C port uses a Silicon Labs CP2104 UART bridge (driver included in the Linux kernel as cp210x). Note: the root console password is firmware-baked and is NOT the same as the web UI password. Serial console access is not required for configuration.
Step 2: Web UI First-Time Login
Default Credentials
| Setting | Value |
|---|---|
| Username | admin |
| Password | Device serial number (e.g., M2XXXXXXXXXXX) |
Browse to http://<gateway_ip> and log in. On first login, you will be forced to change the password. Set a strong password and record it securely.
API Authentication
The web API hashes passwords using the formula:
SHA256("MatchX.<password>.Gateway")
To generate the hash for API access:
echo -n "MatchX.<your_password>.Gateway" | sha256sum | awk '{print $1}'
Login endpoint:
GET /api/login/admin/<sha256_hash>
This returns a JWT token:
{"ret": true, "token": "<JWT>", "exp": <timestamp>, "firstLogin": false}
Use the token in subsequent requests as: Authorization: Bearer <token>
Step 3: Create the LoRa Configuration
On a factory-fresh gateway, /data/global_conf.json does not exist, causing ENOENT errors when accessing the LoRa endpoints. This file must be created first by POSTing to /api/loraConfig.
This must be done BEFORE setting the LoRa server or RF configuration.
Send a POST to /api/loraConfig with the complete JSON configuration:
{
"SX130x_conf": {
"com_type": "SPI",
"com_path": "/dev/spidev0.0",
"lorawan_public": true,
"clksrc": 0,
"antenna_gain": 0,
"full_duplex": false,
"radio_0": {
"enable": true,
"type": "SX1250",
"freq": 867500000,
"rssi_offset": -215.4,
"rssi_tcomp": {"coeff_a": 0, "coeff_b": 0, "coeff_c": 20.41, "coeff_d": 2162.56, "coeff_e": 0},
"tx_enable": true,
"tx_freq_min": 863000000,
"tx_freq_max": 870000000,
"tx_gain_lut": [
{"rf_power": 12, "pa_gain": 0, "pwr_idx": 15},
{"rf_power": 13, "pa_gain": 0, "pwr_idx": 16},
{"rf_power": 14, "pa_gain": 0, "pwr_idx": 17},
{"rf_power": 15, "pa_gain": 0, "pwr_idx": 19},
{"rf_power": 16, "pa_gain": 0, "pwr_idx": 20},
{"rf_power": 17, "pa_gain": 0, "pwr_idx": 22},
{"rf_power": 18, "pa_gain": 1, "pwr_idx": 1},
{"rf_power": 19, "pa_gain": 1, "pwr_idx": 2},
{"rf_power": 20, "pa_gain": 1, "pwr_idx": 3},
{"rf_power": 21, "pa_gain": 1, "pwr_idx": 4},
{"rf_power": 22, "pa_gain": 1, "pwr_idx": 5},
{"rf_power": 23, "pa_gain": 1, "pwr_idx": 6},
{"rf_power": 24, "pa_gain": 1, "pwr_idx": 7},
{"rf_power": 25, "pa_gain": 1, "pwr_idx": 9},
{"rf_power": 26, "pa_gain": 1, "pwr_idx": 11},
{"rf_power": 27, "pa_gain": 1, "pwr_idx": 14}
]
},
"radio_1": {
"enable": true,
"type": "SX1250",
"freq": 868500000,
"rssi_offset": -215.4,
"rssi_tcomp": {"coeff_a": 0, "coeff_b": 0, "coeff_c": 20.41, "coeff_d": 2162.56, "coeff_e": 0},
"tx_enable": false
},
"chan_multiSF_0": {"enable": true, "radio": 1, "if": -400000},
"chan_multiSF_1": {"enable": true, "radio": 1, "if": -200000},
"chan_multiSF_2": {"enable": true, "radio": 1, "if": 0},
"chan_multiSF_3": {"enable": true, "radio": 0, "if": -400000},
"chan_multiSF_4": {"enable": true, "radio": 0, "if": -200000},
"chan_multiSF_5": {"enable": true, "radio": 0, "if": 0},
"chan_multiSF_6": {"enable": true, "radio": 0, "if": 200000},
"chan_multiSF_7": {"enable": true, "radio": 0, "if": 400000},
"chan_Lora_std": {"enable": true, "radio": 1, "if": -200000, "bandwidth": 250000, "spread_factor": 7},
"chan_FSK": {"enable": true, "radio": 1, "if": 300000, "bandwidth": 125000, "datarate": 50000}
},
"gateway_conf": {
"gateway_ID": "<YOUR_GATEWAY_ID>",
"server_address": "<YOUR_CHIRPSTACK_IP>",
"serv_port_up": 1700,
"serv_port_down": 1700,
"keepalive_interval": 10,
"stat_interval": 30,
"push_timeout_ms": 100,
"forward_crc_valid": true,
"forward_crc_error": false,
"forward_crc_disabled": false
}
}
Replace <YOUR_GATEWAY_ID> with the gateway EUI registered in ChirpStack and <YOUR_CHIRPSTACK_IP> with your ChirpStack Gateway Bridge IP address.
Step 4: Set the LoRa Server Configuration
POST to /api/loraServer:
{
"autoConfig": false,
"gateway_ID": "<YOUR_GATEWAY_ID>",
"server_address": "<YOUR_CHIRPSTACK_IP>",
"serv_port_up": 1700,
"serv_port_down": 1700,
"stat_interval": 30,
"push_timeout_ms": 100
}
CRITICAL: autoConfig must be false. When set to true, the firmware overrides the server settings and the packet forwarder becomes unstable.
Step 5: Set the RF Configuration
POST to /api/loraRf/0:
{
"autoConfig": true,
"radio_0": {"freq": 867500000, "tx_freq_min": 863000000, "tx_freq_max": 870000000},
"radio_1": {"freq": 868500000},
"chan_multiSF_0": {"enable": true, "radio": 1, "if": -400000},
"chan_multiSF_1": {"enable": true, "radio": 1, "if": -200000},
"chan_multiSF_2": {"enable": true, "radio": 1, "if": 0},
"chan_multiSF_3": {"enable": true, "radio": 0, "if": -400000},
"chan_multiSF_4": {"enable": true, "radio": 0, "if": -200000},
"chan_multiSF_5": {"enable": true, "radio": 0, "if": 0},
"chan_multiSF_6": {"enable": true, "radio": 0, "if": 200000},
"chan_multiSF_7": {"enable": true, "radio": 0, "if": 400000},
"chan_Lora_std": {"enable": true, "radio": 1, "if": -200000, "bandwidth": 250000, "spread_factor": 7},
"chan_FSK": {"enable": true, "radio": 1, "if": 300000, "bandwidth": 125000, "datarate": 50000}
}
CRITICAL: RF autoConfig must be true. This lets the firmware handle the radio hardware initialisation using its built-in calibration data. Setting it to false causes the concentrator to crash and enter a reset loop.
Step 6: DNS Fix for ps-glo.matchx.io (Essential)
This is the most critical step for stable operation.
The Problem
The MatchX firmware runs several background daemons:
| Daemon | Purpose |
|---|---|
tlsDaemon |
Connects to ps-glo.matchx.io:1010 via TLS for provisioning using the A71CH hardware security chip |
gwconfd |
Gets supernode address from tlsDaemon; manages packet forwarder lifecycle |
loraMgmtDaemon |
Periodically reloads config and resets the LoRa concentrator (SX1302) |
netMgmtDaemon |
Monitors network connectivity |
The tlsDaemon repeatedly tries to connect to ps-glo.matchx.io:1010, which no longer exists (NXDOMAIN). The DNS resolution timeout causes gwconfd to cycle, which triggers loraMgmtDaemon to reset the concentrator, crashing the packet forwarder after approximately 60 seconds.
The Solution
Run dnsmasq on a local server to resolve ps-glo.matchx.io to 127.0.0.1. This makes the TLS connection fail instantly (connection refused) instead of timing out on DNS, preventing the crash cascade.
dnsmasq Configuration
Save to /etc/dnsmasq.d/matchx-gateway.conf:
listen-address=<YOUR_DNS_SERVER_IP>
bind-interfaces
address=/ps-glo.matchx.io/127.0.0.1
address=/matchx.local/127.0.0.1
server=<YOUR_UPSTREAM_DNS>
no-resolv
no-dhcp-interface=*
port=53
log-queries
systemd Service
Save to /etc/systemd/system/dnsmasq-matchx.service:
[Unit]
Description=dnsmasq DNS for MatchX gateways
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
ExecStart=/usr/sbin/dnsmasq -C /etc/dnsmasq.d/matchx-gateway.conf --no-daemon --log-facility=/var/log/dnsmasq-matchx.log
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
Enable and start:
sudo systemctl enable --now dnsmasq-matchx.service
Point Gateways to Local DNS
POST to /api/miscNetwork/dns:
{"dnsManual": true, "dns": "<YOUR_DNS_SERVER_IP>"}
Reboot the gateway after setting DNS.
Step 7: Reboot the Gateway
POST to /api/reboot:
{"magicCode": "d8ZT8X4fzM45fJ7F"}
The gateway takes approximately 90 seconds to fully boot and start the packet forwarder.
Step 8: Verification
Check System Info (No Auth Required)
GET /api/getSystemInfo
Returns model, serial number, firmware version, and timestamp.
Check LoRa Status (Auth Required)
GET /api/status
Verify these fields:
| Field | Expected Value |
|---|---|
loraRunning |
True |
loraTxPkt |
Incrementing (downlinks) |
loraRxPkt |
Incrementing (uplinks from devices) |
loraStatInterval |
30 (matches config) |
ChirpStack Verification
In ChirpStack, navigate to Gateways and confirm the gateway shows as “online”. The gateway should send stat packets every 30 seconds. Device uplinks should appear in the gateway’s frame log with the correct gateway ID in the rxInfo array.
Complete API Reference
| Endpoint | Method | Auth | Description |
|---|---|---|---|
/api/getSystemInfo |
GET | No | System info (model, serial, version) |
/api/login/<user>/<hash> |
GET | No | Login, returns JWT token |
/api/status |
GET | Yes | Full status including LoRa, GPS, sensors |
/api/loraConfig |
GET/POST | Yes | Read/write raw global_conf.json |
/api/loraServer |
GET/POST | Yes | LoRa server settings |
/api/loraRf/0 |
GET/POST | Yes | RF channel 0 configuration |
/api/miscNetwork |
GET | Yes | Network misc settings (DNS, NTP) |
/api/miscNetwork/dns |
POST | Yes | Set DNS configuration |
/api/wiredNetwork |
GET | Yes | Ethernet settings |
/api/wirelessNetwork |
GET | Yes | WiFi settings |
/api/reboot |
POST | Yes | Reboot (requires magicCode) |
/api/remoteAssistance/QUERY |
GET | No | Check remote assistance status |
/api/remoteAssistance/DISABLE |
GET | No | Disable remote assistance |
Troubleshooting
ENOENT Error on loraServer or loraConfig GET
The /data/global_conf.json file does not exist. POST a complete configuration to /api/loraConfig first (Step 3).
Packet Forwarder Crashes / loraRunning is False
Check in this order:
- DNS is set to a server that resolves
ps-glo.matchx.ioto127.0.0.1 loraServerautoConfigisfalseloraRf/0autoConfigistrue- The
SX130x_confsection includes a validtx_gain_lutwith 16 entries gateway_IDis set in bothloraConfigandloraServer
CoreCell Reset Loop on Serial Console
Repeated messages like CoreCell reset through GPIO129... CoreCell power enable through GPIO97... SX1261 reset through GPIO79... indicate the loraMgmtDaemon is cycling the concentrator. This is caused by the DNS/provisioning issue. Apply the DNS fix (Step 6).
Authentication Error on API
JWT tokens expire. Re-login to get a fresh token. On a freshly reset gateway, use admin / <serial number> for first login.
Gateway Shows Offline in ChirpStack After Running Initially
The packet forwarder crashes when gwconfd cannot reach ps-glo.matchx.io. Ensure the dnsmasq service is running and the gateway’s DNS is pointed to it. Verify: dig @<dns_server_ip> ps-glo.matchx.io should return 127.0.0.1.
Monitoring Script
Use this bash script to check gateway status. Run manually or schedule via cron:
#!/bin/bash
HASH=$(echo -n 'MatchX.<YOUR_PASSWORD>.Gateway' | sha256sum | awk '{print $1}')
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$TIMESTAMP] Gateway Status Check"
for gw in "<SERIAL> <IP> <GWID>"; do
name=$(echo $gw | awk '{print $1}')
ip=$(echo $gw | awk '{print $2}')
gwid=$(echo $gw | awk '{print $3}')
TOKEN=$(curl -sk --connect-timeout 5
"http://${ip}/api/login/admin/${HASH}"
-H "Content-Type: application/json" 2>&1 |
grep -oP '"token":"[^"]*"' | sed 's/"token":"//;s/"//')
if [ -z "$TOKEN" ]; then
echo " $name ($ip) [$gwid]: UNREACHABLE"
else
result=$(curl -sk --connect-timeout 5
"http://${ip}/api/status"
-H "Authorization: Bearer $TOKEN"
-H "Content-Type: application/json" 2>&1)
running=$(echo "$result" | python3 -c
"import sys,json; print(json.load(sys.stdin)['result']['loraRunning'])"
2>/dev/null || echo "ERROR")
tx=$(echo "$result" | python3 -c
"import sys,json; print(json.load(sys.stdin)['result']['loraTxPkt'])"
2>/dev/null || echo "?")
rx=$(echo "$result" | python3 -c
"import sys,json; print(json.load(sys.stdin)['result']['loraRxPkt'])"
2>/dev/null || echo "?")
echo " $name ($ip) [$gwid]: loraRunning=$running TX=$tx RX=$rx"
fi
done
Summary of Configuration Order
- Connect gateway to Ethernet, find its IP via DHCP
- Login to web UI via browser, change default password
- Set up dnsmasq on a local server to block
ps-glo.matchx.io - POST to
/api/loraConfigwith full SX130x + gateway config (creates the missing file) - POST to
/api/loraServerwithautoConfig: false - POST to
/api/loraRf/0withautoConfig: true - POST to
/api/miscNetwork/dnsto point DNS to your dnsmasq server - POST to
/api/rebootwith the magic code - Wait 90 seconds, verify
loraRunning: truevia/api/status - Confirm gateway appears online in ChirpStack
This guide was created based on hands-on configuration of MatchX MX1901 gateways running firmware 4.1.15-2.0.1 (Web UI v0.13.00) connected to ChirpStack v4. The MatchX provisioning infrastructure (ps-glo.matchx.io) was confirmed non-operational as of March 2026.
