Overview
In industrial IoT labs, private LoRaWAN networks, field demonstrations, and edge deployments, relying on a cloud-based LoRaWAN Network Server is not always feasible. Connectivity may be limited, the customer may require local data processing, or the solution may need to continue operating inside an isolated network.
This guide explains how to deploy The Things Stack Open Source Edition locally on a Robustel EG3120 edge gateway and connect a Robustel R1320LG LoRaWAN gateway to the EG3120 as its local LoRaWAN Network Server (LNS).
After completing this guide, you will be able to:
- Install Docker Engine and Docker Compose on the EG3120.
- Deploy The Things Stack with PostgreSQL and Redis.
- Configure the required ports for Console, MQTT, gRPC, UDP Packet Forwarder, and LoRa Basics Station.
- Create the initial administrator account and Console OAuth client.
- Create an application, gateway, and OTAA end device in The Things Stack Console.
- Configure the R1320LG gateway to connect to the EG3120 LNS.
- Verify gateway connection, OTAA join, uplink traffic, and downlink traffic.
- Troubleshoot common deployment and connectivity issues.
Network Topology
Example addressing used in this guide:
Device or Service | Example Address |
EG3120 local management address | 192.168.0.1 |
EG3120 field network address | 172.17.100.55 |
The Things Stack Console | |
LoRa Basics Station LNS with TLS | wss://172.17.100.55:8887 |
LoRa Basics Station LNS without TLS | ws://172.17.100.55:1887 |
Note: The R1320LG should use the EG3120 field network address, for example 172.17.100.55, because the gateway is connected to the 172.17.100.x network. The 192.168.0.1 address is normally used only for direct local maintenance from a laptop.
What You'll Need
- Hardware List:
- 1 x Robustel EG3120 edge gateway acting as the LNS server.
- 1 x Robustel R1320LG LoRaWAN gateway.
- 1 x Standard LoRaWAN end device. OTAA is recommended.
- Proper LoRa antennas for both the R1320LG gateway and the LoRaWAN end device.
- Software/Firmware Requirements:
- EG3120 running RobustOS Pro with Debian-based Linux.
- R1320LG firmware supporting LoRa Basics Station or UDP Packet Forwarder.
- Docker Engine.
- Docker Compose plugin.
- The Things Stack Open Source Edition.
- SSH client.
- Web browser.
- Minimum EG3120 System Requirements:
- CPU: dual-core minimum, quad-core recommended.
- RAM: 2 GB minimum, 4 GB or higher recommended.
- Storage: 8 GB free minimum, 16 GB or higher recommended.
- Correct system time.
- Working DNS resolution.
- Internet connectivity for online installation, unless using offline Debian packages and preloaded Docker images.
- Network Requirements:
- Use a static IP address or stable DNS name for the EG3120 before generating certificates.
- Ensure the R1320LG can reach the EG3120 field network address.
- Ensure the required TCP and UDP ports are allowed by the EG3120 firewall and any upstream firewall.
Required ports:
Port | Protocol | Purpose |
1700 | UDP | Semtech UDP Packet Forwarder |
1881 | TCP | Gateway MQTT v2 |
1882 | TCP | Gateway MQTT v3 |
1883 or custom | TCP | Application MQTT |
1884 | TCP | gRPC |
1885 | TCP | Web Console / HTTP API |
1887 | TCP | LoRa Basics Station LNS without TLS |
8887 | TCP | LoRa Basics Station LNS with TLS |
If an existing MQTT broker already uses 127.0.0.1:1883, map The Things Stack Application MQTT to another external port, for example 18830.
Step-by-Step Configuration Guide
Phase 1: Install Docker and Compose on the EG3120
- Connect to the EG3120 via SSH. If a sudo user has not been created, refer to the Robustel Knowledge Base article: How to Create a Sudo User with SSH Access on an EG Series Device.
1 ssh robustel@192.168.0.1- Update the package index and install prerequisite tools.
1 sudo apt-get update2 sudo apt-get install -y ca-certificates curl gnupg lsb-release- Create the Docker apt keyring directory.
1 sudo install -m 0755 -d /etc/apt/keyrings- Add Docker's official GPG key.
1 curl -fsSL https://download.docker.com/linux/debian/gpg | \2 sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg3 sudo chmod a+r /etc/apt/keyrings/docker.gpg- Add Docker's official apt repository.
1 echo \2 "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian \3 $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \4 sudo tee /etc/apt/sources.list.d/docker.list > /dev/null- Install Docker Engine and the Docker Compose plugin.
1 sudo apt-get update2 sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin- Enable and start Docker.
1 sudo systemctl enable docker2 sudo systemctl start docker- Verify the installation.
1 sudo docker version2 sudo docker compose versionExpected output:
1 Docker Server: 24.x.x2 Docker Compose version v2.x.xPhase 2: Deploy The Things Stack
- Create the deployment directory and navigate into it.
1 sudo mkdir -p /opt/tts2 sudo chown -R "$USER":"$USER" /opt/tts3 cd /opt/tts- Download the official Docker Compose file from The Things Stack repository.
1 curl -L -o docker-compose.yml \2 https://raw.githubusercontent.com/TheThingsNetwork/lorawan-stack/v3.36.1/docker-compose.ymlThis guide pins The Things Stack to the validated 3.36.1 release. Review release notes and retest the deployment before changing this version.
- Replace the downloaded development-oriented Compose content with the following validated edge deployment layout. Set the same PostgreSQL password here and in is.database-uri in the next configuration file.
1 services: 2 postgres: 3 image: postgres:15-alpine 4 container_name: tts-postgres 5 restart: unless-stopped 6 environment: 7 POSTGRES_USER: ttn 8 POSTGRES_PASSWORD: REPLACE_DB_PASSWORD 9 POSTGRES_DB: ttn_lorawan10 volumes:11 - ./data/postgres:/var/lib/postgresql/data12 13 redis:14 image: redis:7-alpine15 container_name: tts-redis16 restart: unless-stopped17 command: redis-server --appendonly yes18 volumes:19 - ./data/redis:/data20 21 stack:22 image: thethingsnetwork/lorawan-stack:3.36.123 container_name: tts-stack24 restart: unless-stopped25 depends_on:26 - postgres27 - redis28 entrypoint: ttn-lw-stack -c /config/ttn-lw-stack.yml29 command: start30 volumes:31 - ./config:/config:ro32 - ./data/blob:/srv/ttn-lorawan/public/blob33 ports:34 - "1700:1700/udp"35 - "1881:1881"36 - "1882:1882"The mapping 18830:1883 is only needed when another MQTT service on EG3120 already occupies port 1883.
- Create the configuration directory and data directory.
1 mkdir -p config/certs data/blob data/postgres data/redis- Generate the cluster key and cookie keys for The Things Stack.
1 openssl rand -hex 322 openssl rand -hex 323 openssl rand -hex 64Use the first value as the cluster key, the second value as block-key, and the third value as hash-key in the configuration file.
- Create the The Things Stack configuration file.
1 nano config/ttn-lw-stack.ymlExample configuration:
1 log: 2 level: info 3 4 cluster: 5 name: eg3120 6 keys: 7 - "REPLACE_WITH_32_BYTE_CLUSTER_KEY" 8 9 http:10 listen: ":1885"11 cookie:12 block-key: "REPLACE_WITH_32_BYTE_HEX"13 hash-key: "REPLACE_WITH_64_BYTE_HEX"14 15 grpc:16 listen: ":1884"17 18 redis:19 address: "redis:6379"20 21 cache:22 service: redis23 redis:24 address: "redis:6379"25 26 events:27 backend: redis28 redis:29 address: "redis:6379"30 31 is:32 database-uri: "postgresql://ttn:REPLACE_DB_PASSWORD@postgres:5432/ttn_lorawan?sslmode=disable"33 network:34 net-id: "000000"35 email:36 sender-name: "EG3120 The Things Stack"Replace the following values before starting the stack:
Placeholder | Description |
REPLACE_DB_PASSWORD | PostgreSQL password used by the stack |
REPLACE_WITH_32_BYTE_CLUSTER_KEY | Random cluster key |
REPLACE_WITH_32_BYTE_HEX | HTTP cookie block key |
REPLACE_WITH_64_BYTE_HEX | HTTP cookie hash key |
172.17.100.55 | EG3120 field network IP address or FQDN |
EB1E9244E686BD11/64 | JoinEUI prefix handled by the local Join Server; replace or extend this list for the deployed devices |
The js.join-eui-prefix and console.ui.dcs.base-url settings are required for the normal Console registration workflow. Without the matching JoinEUI prefix, the Join Server does not advertise ownership of the device's JoinEUI. Without the DCS base URL, the Console may generate http://localhost:1885/api/v3 for the Device Claiming API; a browser on another computer then sends the request to that computer instead of the EG3120, and the Confirm button reports that the JoinEUI could not be confirmed.
- Generate local TLS certificates for secure LoRa Basics Station communication.
1 cd /opt/tts/config/certs 2 3 openssl genrsa -out ca.key 4096 4 openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 -out ca.pem \ 5 -subj "/C=CN/ST=Shanghai/L=Shanghai/O=Robustel/OU=EG3120/CN=EG3120 TTS CA" 6 7 openssl genrsa -out server.key 4096 8 cat > server.cnf <<'EOF' 9 [req]10 default_bits = 409611 prompt = no12 default_md = sha25613 distinguished_name = dn14 req_extensions = req_ext15 16 [dn]17 C = CN18 ST = Shanghai19 L = Shanghai20 O = Robustel21 OU = EG312022 CN = 172.17.100.5523 24 [req_ext]25 subjectAltName = @alt_names26 27 [alt_names]28 IP.1 = 172.17.100.5529 DNS.1 = tts-eg3120.local30 EOF31 32 openssl req -new -key server.key -out server.csr -config server.cnf33 openssl x509 -req -in server.csr -CA ca.pem -CAkey ca.key -CAcreateserial \34 -out server.pem -days 3650 -sha256 -extensions req_ext -extfile server.cnf35 36 cd /opt/ttsImportant certificate rules:
- If the R1320LG connects to wss://172.17.100.55:8887, the server certificate must include IP.1 = 172.17.100.55.
- If the R1320LG connects to a DNS name, the server certificate must include that DNS name.
- Upload ca.pem to the R1320LG as the Basic Station trust certificate.
- If the EG3120 IP address changes, regenerate the server certificate and update the R1320LG LNS address.
- Start PostgreSQL and Redis.
1 docker compose up -d postgres redis- Initialize the The Things Stack databases.
1 docker compose run --rm stack is-db migrate2 docker compose run --rm stack js-db migrate3 docker compose run --rm stack ns-db migrate --force4 docker compose run --rm stack as-db migrate --force- Create the initial administrator account.
1 docker compose run --rm stack is-db create-admin-user \2 --id admin \3 --email admin@example.comFollow the password prompt and store the credentials securely.
- Create the Console OAuth client.
1 docker compose run --rm stack is-db create-oauth-client \2 --id console \3 --name "Console" \4 --owner admin \5 --secret console \6 --redirect-uri "http://172.17.100.55:1885/console/oauth/callback" \7 --redirect-uri "/console/oauth/callback" \8 --logout-redirect-uri "http://172.17.100.55:1885/console" \9 --logout-redirect-uri "/console"- Start all The Things Stack services.
1 docker compose up -d- Verify the deployment.
1 docker ps2 docker compose ps3 docker logs --tail 100 tts-stack4 curl -I http://127.0.0.1:1885/consoleA redirect to /console/ is expected from the HTTP check.
Phase 3: Configure The Things Stack Console
- Open a web browser and navigate to the TTS Console URL.
1 http://172.17.100.55:1885/console- Log in using the administrator credentials created in Phase 2.
- Navigate to Gateways and click Add gateway.
- Enter a Gateway ID.
Example:
1 r1320lg-000247- Enter the R1320LG Gateway EUI.
Example:
1 34FA40FFFE000247The Gateway EUI is normally printed on the device label or shown in the R1320LG web UI. It may also appear in the Basic Station log as:
1 Station EUI : 34fa:40ff:fe00:0247Enter it in The Things Stack without separators:
1 34FA40FFFE000247- Select the frequency plan.
Example:
1 Europe 868.1 MHzThe frequency plan must match the R1320LG radio region and the end device region.
- Select LoRa Basics Station as the connection method and save the gateway.
- Generate a gateway API key with the required gateway traffic rights.
Save this key securely. It will be used as the TC Key or Client key on the R1320LG.
Example key format:
1 NNSXS.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx- Navigate to Applications and create a new application.
Example:
1 factory-sensors- Open the application and click Register end device.
- Select manual registration or the device repository profile if available.
- For manual registration, select the frequency plan, LoRaWAN version, and Regional Parameters version that match the physical device.
- Enter the JoinEUI and click Confirm. A correct local deployment displays:
1 This end device can be registered on the networkIf the Console reports that the JoinEUI could not be confirmed, do not bypass this step. Check the js.join-eui-prefix and console.ui.dcs.base-url settings described in Phase 2, restart the stack, refresh the browser page, and retry.
- Register the OTAA sensor by entering its DevEUI, AppKey, and End device ID.
Example values:
Field | Example |
JoinEUI / AppEUI | EB1E9244E686BD11 |
DevEUI | 00010111FF00045D |
AppKey | 2B7E151628AED2A6ABF7158809CF4F3F |
LoRaWAN version | MAC V1.0.2 or as required by the device |
Regional parameters | PHY V1.0.2 REV B or as required by the device |
Activation mode | OTAA |
The JoinEUI, DevEUI, and AppKey must exactly match the values configured in the physical end device.
- After registration, trigger a new OTAA join from the physical end device. Do not use the Generate button for AppKey unless the generated value is also written to the physical device.
Open Source Edition Note: The Things Stack Open Source Edition is typically deployed as a single-tenant environment. Tenant creation is relevant to multi-tenant editions and hosted deployments. For this EG3120 Open Source deployment, create applications, gateways, and end devices directly under the local administrator account.
__
Phase 4: Configure the R1320LG Gateway
- Log in to the R1320LG web interface.
- Navigate to the LoRaWAN Basic Station page.
1 Packet Forwarder / Basic Station- Configure the R1320LG for LoRa Basics Station.
Enable | On |
LoRaWan Public | On |
TLS Enable | On Enabled for wss:// |
Server address | 172.17.100.55 or EG3120 FQDN |
Server port | 8887 for TLS, 1887 for non-TLS |
Trust certificate | Upload the EG3120 CA certificate, for example ca.pem |
Client key / TC key | Gateway API key generated in The Things Stack |
- Upload the ca.pem file generated on the EG3120 into the Trust certificate field.
- Paste the gateway API key generated in The Things Stack into the Client key / TC key field.
- Save and apply the configuration.
- Restart the Basic Station service if required.
Expected R1320LG log:
1 Connecting to INFOS: wss://172.17.100.55:88872 LNS protocol established3 router_config receivedVerification & Testing
To confirm that the configuration is successful, verify the gateway connection first, then verify OTAA join, uplink, and downlink traffic.
Testing Method:
Restart or trigger the LoRaWAN end device to force it to send a Join Request. For a battery-powered sensor, use the vendor's documented restart or join-trigger method.
Check Gateway Online Status:
- In The Things Stack Console, go to Gateways.
- Open the Live data tab.
Expected gateway events:
1 Gateway connection established2 Status received3 Uplink message received4 Downlink message scheduledCheck OTAA Join:
- In The Things Stack Console, go to Applications.
- Open the application and select the end device.
- Open the Live data tab.
Expected OTAA events:
1 Accept join-request2 Successfully processed join-request3 Forward join-accept messageCheck R1320LG Basic Station Status:
Check Downlink Traffic:
- In The Things Stack Console, open the end device.
- Go to Messaging.
- Queue a downlink message.
- For a Class A device, wait for the next uplink because the downlink is transmitted after the device opens its receive window.
Expected downlink events:
1 Schedule downlink message2 Forward downlink messageTroubleshooting / FAQ
Q: The Console says, "There was an error and the JoinEUI could not be confirmed." How do I fix it?
A: This message is normally caused by local Join Server or Device Claiming API configuration, not by the hexadecimal format of the JoinEUI. Make sure the local Join Server advertises the end device's JoinEUI prefix and make sure the Console DCS URL points to the EG3120, not localhost.
1 js:2 join-eui-prefix:3 - "EB1E9244E686BD11/64"4 5 console:6 ui:7 dcs:8 base-url: "http://172.17.100.55:1885/api/v3"Restart the stack and verify the JoinEUI prefix API:
1 cd /opt/tts2 docker compose restart stackExpected response:
1 {"prefixes":[{"join_eui":"EB1E9244E686BD11","length":64}]}Then reload the Console and click Confirm again. The page should display This end device can be registered on the network.
Q: The Join Request reaches The Things Stack but Live data reports MIC mismatch. What does this mean?
A: The radio path and gateway forwarding are already working. MIC mismatch means that the Join Server could not validate the Join Request with the root key registered for that DevEUI. In most cases, the AppKey in The Things Stack differs from the AppKey in the physical device. Compare all 32 hexadecimal characters; a one-byte difference is enough to fail. Also confirm the DevEUI and JoinEUI. After correcting the value, restart or retrigger OTAA on the end device.
Example matching values:
1 JoinEUI: EB1E9244E686BD112 DevEUI: 00010111FF00045D3 AppKey: 2B7E151628AED2A6ABF7158809CF4F3FQ: Why is my R1320LG gateway showing as offline in the Console?
A: Check that the R1320LG can reach the EG3120 field network address. Use the field network address, for example 172.17.100.55, not the laptop-only maintenance address 192.168.0.1. Confirm that port 8887/tcp is open when using Basic Station TLS. Check The Things Stack logs for gateway connection or authentication errors.
Useful commands:
1 ping 172.17.100.552 nc -vz 172.17.100.55 88873 cd /opt/tts4 docker compose ps5 docker logs --tail 100 tts-stackQ: The Docker container is in a restart loop. What should I check?
A: Check the stack, PostgreSQL, and Redis logs. Common causes include invalid YAML syntax, missing certificate files, incomplete database migrations, and incorrect database URI settings.
1 docker ps -a2 docker compose logs --tail 200 stack3 docker compose logs --tail 200 postgres4 docker compose logs --tail 200 redisQ: What should I do if port 1883 is occupied on the EG3120?
A: If a local MQTT broker already uses 127.0.0.1:1883, map The Things Stack Application MQTT to another external port in docker-compose.yml, for example:
1 ports:2 - "18830:1883"Then set the Application Server MQTT public address accordingly:
1 as:2 mqtt:3 public-address: "172.17.100.55:18830"Q: How do I check whether the EG3120 firewall is blocking the connection?
A: Check the firewall rules and listening ports on the EG3120.
1 sudo ss -lntup2 sudo iptables -S3 sudo iptables -L -n -v4 sudo nft list rulesetAt minimum, confirm that the required ports are open:
1 1700/udp2 1885/tcp3 1887/tcp4 8887/tcpQ: Why does the R1320LG report gateway authentication failure?
A: Confirm that the Gateway EUI configured on the R1320LG exactly matches the Gateway EUI registered in The Things Stack. Regenerate the gateway API key if needed and upload or paste it into the R1320LG Client key / TC key field. Confirm that the API key has gateway traffic rights.
Q: The gateway is online, but I see no Join Requests from the sensor. What is wrong?
A: Confirm that proper LoRa antennas are attached to both the R1320LG and the end device. Confirm that the end device is in OTAA mode. Confirm that the device region, gateway region, and The Things Stack frequency plan match, for example EU868. Restart or trigger the device to join and check the R1320LG log for jreq.
Q: I see Join Requests, but there is no Join Accept or no downlink. What should I check?
A: Confirm that gateway TX is enabled and that a LoRa antenna is connected. Confirm that the frequency plan and RX2 settings match the device region. Check R1320LG logs for dnmsg, starting TX, and dntxed.
Q: Why do I see DNS or NTP errors on an offline network?
A: If the deployment network has no Internet access, configure local DNS and NTP services. Certificate validation and LoRa Basics Station timing behavior can be affected by incorrect system time.
Common log messages:
1 Can't find host2 Name or service not known3 NTP client synchronization failed4 Time sync rejectedQ: Why does Basic Station report a TLS certificate Common Name or hostname mismatch?
A: The R1320LG server address must match a DNS or IP Subject Alternative Name in the server certificate. If R1320LG connects to wss://172.17.100.55:8887, the server certificate must include:
1 IP.1 = 172.17.100.55Regenerate the certificate after changing the EG3120 IP address or hostname.
Certificate check commands:
1 openssl x509 -in config/certs/ca.pem -noout -subject -issuer -dates2 openssl x509 -in config/certs/server.pem -noout -subject -issuer -dates -ext subjectAltName3 openssl s_client -connect 172.17.100.55:8887 -CAfile config/certs/ca.pemQ: Can the EG3120 run multiple applications?
A: Yes. The Things Stack supports multiple applications. Actual capacity depends on the number of gateways, number of devices, traffic volume, integrations, payload processing, and EG3120 hardware resources.
Q: How many gateways are supported?
A: There is no single fixed number for all deployments. For small private networks and demonstrations, EG3120 can run The Things Stack for one or several gateways. For larger deployments, validate CPU, memory, storage I/O, and uplink/downlink load under expected traffic.
Q: How do I upgrade The Things Stack?
A: Back up configuration and PostgreSQL data first, review the target release notes, pull the target container image, run database migrations if required, and restart the stack.
Example:
1 cd /opt/tts2 docker compose pull3 docker compose run --rm stack is-db migrate4 docker compose up -dQ: How do I back up the configuration?
A: Back up the Docker Compose file, The Things Stack configuration file, certificate directory, and PostgreSQL data.
Files to back up:
1 /opt/tts/docker-compose.yml2 /opt/tts/config/ttn-lw-stack.yml3 /opt/tts/config/certs/Example PostgreSQL backup:
1 cd /opt/tts2 docker compose exec postgres pg_dump -U root ttn_lorawan > ttn-lorawan-backup.sqlQ: Can the deployment run without Internet access?
A: Yes, but the required Docker images, Debian packages, frequency plan files, certificates, and NTP source must be prepared in advance. For offline deployments, use an offline package repository, a local Docker registry, or a preloaded Docker image archive.
Q: Should I use LoRa Basics Station or UDP Packet Forwarder?
A: Use LoRa Basics Station for new deployments. UDP Packet Forwarder is useful for compatibility with legacy gateways, but Basic Station provides stronger security and operational behavior, especially when TLS is enabled.
Q: What should I do if the EG3120 IP address changes?
A: Update the R1320LG LNS server address, The Things Stack Console canonical URL, OAuth redirect URI, public addresses, firewall rules, and TLS server certificate. For stable deployments, use a DNS name instead of a changing IP address.
Useful Commands
Docker and The Things Stack:
1 cd /opt/tts2 docker ps3 docker ps -a4 docker compose ps5 docker compose logs --tail 200 stack6 docker compose restart stack7 docker compose down8 docker compose up -dSystem Logs:
1 journalctl -u docker --no-pager -n 1002 journalctl -xe --no-pagerNetwork Checks:
1 ip addr2 ip route3 ping 172.17.100.554 curl -I http://127.0.0.1:1885/console5 curl -I http://172.17.100.55:1885/console6 sudo ss -lntup7 sudo ss -lnuFirewall Checks:
1 sudo iptables -S2 sudo iptables -L -n -v3 sudo nft list rulesetCertificate Checks:
1 openssl x509 -in config/certs/ca.pem -noout -subject -issuer -dates2 openssl x509 -in config/certs/server.pem -noout -subject -issuer -dates -ext subjectAltName3 openssl s_client -connect 172.17.100.55:8887 -CAfile config/certs/ca.pemReferences
- Docker Engine installation documentation for Debian: https://docs.docker.com/engine/install/debian/
- Docker Compose documentation: https://docs.docker.com/compose/
- The Things Stack documentation: https://www.thethingsindustries.com/docs/
- LoRa Basics Station documentation: https://doc.sm.tc/station/