MatchX MX190x (M2 Pro) Gateway – Complete ChirpStack Configuration Guide

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:

  1. DNS is set to a server that resolves ps-glo.matchx.io to 127.0.0.1
  2. loraServer autoConfig is false
  3. loraRf/0 autoConfig is true
  4. The SX130x_conf section includes a valid tx_gain_lut with 16 entries
  5. gateway_ID is set in both loraConfig and loraServer

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

  1. Connect gateway to Ethernet, find its IP via DHCP
  2. Login to web UI via browser, change default password
  3. Set up dnsmasq on a local server to block ps-glo.matchx.io
  4. POST to /api/loraConfig with full SX130x + gateway config (creates the missing file)
  5. POST to /api/loraServer with autoConfig: false
  6. POST to /api/loraRf/0 with autoConfig: true
  7. POST to /api/miscNetwork/dns to point DNS to your dnsmasq server
  8. POST to /api/reboot with the magic code
  9. Wait 90 seconds, verify loraRunning: true via /api/status
  10. 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.

Leave a Reply