Applications AWS

ChirpStack LoRaWAN Network 4.20.0 on AWS User Guide

| Product: ChirpStack LoRaWAN Network 4.20.0 on Ubuntu 24.04 on AWS

Overview

This guide covers launching and using the ChirpStack LoRaWAN Network 4.20.0 on Ubuntu 24.04 AMI from cloudimg on Amazon EC2. It is one version of the cloudimg Hardened EHRbase & GIS, Health and Research Platforms AMIs listing on AWS Marketplace; you pick it as the software version when you subscribe and launch.

ChirpStack is the open source LoRaWAN Network Server (MIT licence): it manages your LoRa gateways, devices (OTAA and ABP), device profiles and applications, handles joins, uplinks and downlinks, and delivers device events to your applications over MQTT, HTTP and other integrations. The ChirpStack Gateway Bridge converts your gateways' packet-forwarder traffic into MQTT for ChirpStack.

The image is the network server, not the radio hardware: you also need LoRa gateways pointed at this instance (Step 9).

The cloudimg image adds:

  • No default admin and no demo data. ChirpStack's database migrations create the user admin with the published password admin. On this image, your instance's first boot runs the migrations itself and gives admin a random 24-character password before any port opens; admin/admin is refused. No applications, devices, gateways or API keys ship.
  • Secrets of your instance's own. The admin password, ChirpStack's API signing secret (api.secret, which signs every console token and API key), the three MQTT account passwords, the Valkey password and the TLS key are generated on your instance. A token or API key minted on another ChirpStack instance is refused here.
  • HTTPS from the first boot. nginx serves the console, gRPC-web and the native gRPC API on port 443 with a certificate generated on your instance; port 80 redirects to HTTPS and a request for an unknown host name gets no answer. ChirpStack itself listens only on 127.0.0.1:8080. Sign in is rate limited per address.
  • An MQTT broker with no anonymous access. Mosquitto 2.1 (from the Eclipse Mosquitto maintainers' package archive) listens on 127.0.0.1:1883 only, refuses anonymous clients, and has three accounts with passwords of your instance's own and their own topic rights: chirpstack (the network server), gateway-bridge (gateway topics only) and integration for your application (it reads device events and queues downlinks, and cannot read or write gateway traffic).
  • A private, password-protected device-session store. Device sessions live in Valkey 7.2 (the open source Redis successor) in memory only, with no network listener: a unix socket that only Valkey and ChirpStack can open. Valkey's default (passwordless) user is off; ChirpStack signs in as its own account, which may not run admin, scripting or other dangerous commands.
  • A database with no network listener and no password. PostgreSQL 16 listens on a unix socket only and ChirpStack signs in as its own operating-system user (peer authentication); no database password exists to leak. The image ships no database: your first boot creates it.
  • Integrations that cannot reach inside. ChirpStack's HTTP and other integrations send to URLs a tenant user types in. By default ChirpStack may connect only to loopback and public IPv4 endpoints: private, link-local and carrier-grade NAT IPv4 ranges, your VPC's own IPv4 CIDR and all IPv6 are denied, so an integration cannot reach the instance metadata service or hosts on your private networks (Step 11 shows how to allow one endpoint in your VPC, or IPv6).
  • Hardened defaults. ChirpStack, the bridge, Mosquitto, nginx, Valkey, PostgreSQL and the first boot service are blocked from the instance metadata service, and the instance requires IMDSv2. Configuration is owned by root and read only to the services. The ChirpStack package repository key is pinned by its fingerprint.
  • Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on.

What is on the image: ChirpStack 4.20.0, ChirpStack Gateway Bridge 4.1.2, Mosquitto 2.1.2, PostgreSQL 16, Valkey 7.2.13, nginx, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent.

Default user: ubuntu · Architecture: x86_64 · Recommended instance type: m5.large

Prerequisites

  • An AWS account subscribed to the listing, an EC2 key pair and a VPC subnet with a route to the internet (or a private route you use for management).
  • A security group that allows TCP 22 (SSH), TCP 443 (console and API), TCP 80 (redirects to HTTPS) and UDP 1700 (your gateways) from the networks that should reach them. Keep 22 to your own networks, 443 to the networks your staff and applications use, and UDP 1700 to your gateways' addresses (see Step 9 for why).

Step 1: Launch the instance

In the AWS Marketplace listing choose Continue to Subscribe, then Continue to Configuration, pick ChirpStack LoRaWAN Network 4.20.0 on Ubuntu 24.04 as the software version and your Region, then Launch through EC2. Choose m5.large, your key pair and a security group as above.

With the AWS CLI (replace the values in angle brackets):

aws ec2 run-instances --region <region> --image-id <ami-id-from-the-listing> \
  --instance-type m5.large --key-name <your-key-pair> \
  --security-group-ids <your-security-group> --subnet-id <your-subnet> \
  --metadata-options HttpTokens=required,HttpEndpoint=enabled \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=chirpstack}]'

First boot takes about a minute after the instance is running (it creates the database and runs ChirpStack's migrations).

Step 2: Connect to your instance

Version SSH user
ChirpStack LoRaWAN Network 4.20.0 on Ubuntu 24.04 ubuntu
ssh -i <your-key>.pem ubuntu@<public-ip-or-dns>

Step 3: Check the services

PostgreSQL, Valkey, Mosquitto, ChirpStack, the Gateway Bridge and nginx should all be active:

systemctl is-active postgresql@16-main valkey-server mosquitto chirpstack chirpstack-gateway-bridge nginx
active
active
active
active
active
active

Only SSH, HTTP and HTTPS listen on the network over TCP. ChirpStack and Mosquitto listen on 127.0.0.1, and PostgreSQL and Valkey have no TCP listener at all (each uses a local socket):

sudo ss -tlnH | awk '{print $4}' | sort -u
0.0.0.0:22
0.0.0.0:443
0.0.0.0:80
127.0.0.1:1883
127.0.0.1:8080
127.0.0.53%lo:53
127.0.0.54:53
[::]:22
[::]:443
[::]:80

The Gateway Bridge takes your gateways' packets on UDP 1700:

sudo ss -ulnpH | grep ':1700 '
UNCONN 0      0                       *:1700       *:* users:(("chirpstack-gate",pid=19436,fd=3))

Step 4: Get your sign-in details

The credentials file holds the address ChirpStack is served as (your instance's public DNS name by default), the admin user and its password, and the MQTT account for your application, all generated on this instance:

sudo cat /root/chirpstack-credentials.txt
# ChirpStack on this instance (cloudimg). Root-only (0600). Generated at first boot: 2026-11-07T23:09:37Z
CHIRPSTACK_URL=https://ec2-54-146-215-79.compute-1.amazonaws.com/
CHIRPSTACK_ADMIN_USER=admin
CHIRPSTACK_ADMIN_PASSWORD=<CHIRPSTACK_ADMIN_PASSWORD>
# MQTT broker (loopback only: 127.0.0.1:1883). For your application on this instance (reads application/#,
# writes application/+/device/+/command/down):
MQTT_INTEGRATION_USER=integration
MQTT_INTEGRATION_PASSWORD=<MQTT_INTEGRATION_PASSWORD>
# The chirpstack and gateway-bridge MQTT accounts, the Valkey account and api.secret are in /etc/chirpstack/*.toml,
# /etc/chirpstack-gateway-bridge/chirpstack-gateway-bridge.toml and /etc/chirpstack-cloudimg (root / service only).
# Gateways: Semtech UDP packet forwarder to <this host>:1700, region eu868 (sudo chirpstack-cloudimg-set-gateway-region).
# Change a password: sudo chirpstack-cloudimg-set-password [email]   Change the hostname: sudo chirpstack-cloudimg-set-hostname <name>

Step 5: Sign in to the console

Open CHIRPSTACK_URL in a browser. The certificate is self signed, so the browser warns once; continue to the site (Step 12 replaces it with your own). Visiting the instance by its IP address redirects to the configured name.

The ChirpStack sign in page

Sign in as admin with the password from the credentials file. ChirpStack opens on the dashboard of its one tenant, ChirpStack; the menu on the left has the network-server pages (tenants, users, API keys, device profiles, regions) and the tenant's pages (gateways, device profiles, applications).

The ChirpStack tenant dashboard after sign in

A new instance has the tenant and the admin user only. Set up your network in this order:

  1. Tenant > Gateways > Add gateway: the gateway's EUI (its Gateway ID) and a name. The gateway shows Never seen until it sends its first packet (Step 9).
  2. Tenant > Device profiles > Add device profile: the region (EU868 is enabled for the gateway bridge by default; Step 9 changes it), the LoRaWAN MAC version and regional-parameters revision of your devices, and whether they join by OTAA.
  3. Tenant > Applications > Add application, then Add device in it: the device's DevEUI and profile, then its application key (OTAA) under OTAA keys.
  4. Network Server > Users: create an account for each member of staff with only the rights they need, and keep admin for administration.

The screenshots below show a test gateway and a test device.

A gateway in ChirpStack's gateway list

An application's device list

Step 6: Check HTTPS and the sign in

Port 80 redirects to HTTPS on the configured name, the console answers on it with the instance's certificate, and a request for any other host name is closed without an answer:

curl -s -o /dev/null -w '%{http_code} %{redirect_url}\n' http://127.0.0.1/
H=$(sed -n 's/^set \$cs_host "\(.*\)";$/\1/p' /etc/nginx/chirpstack-host.conf)
curl -s --cacert /etc/nginx/ssl/chirpstack.crt --resolve "$H:443:127.0.0.1" -o /dev/null -w 'console %{http_code}\n' "https://$H/"
curl -sk -o /dev/null -w 'unknown host %{http_code}\n' -H 'Host: other.example' https://127.0.0.1/ || echo '(closed without an answer, as expected)'
301 https://ec2-54-146-215-79.compute-1.amazonaws.com/
console 200
unknown host 000
(closed without an answer, as expected)

(000 means nginx closed the connection without an answer.) The certificate was generated on your instance for its name and addresses:

openssl x509 -noout -subject -ext subjectAltName -in /etc/nginx/ssl/chirpstack.crt
subject=CN = ec2-54-146-215-79.compute-1.amazonaws.com, O = cloudimg
X509v3 Subject Alternative Name: 
    DNS:ec2-54-146-215-79.compute-1.amazonaws.com, IP Address:54.146.215.79, IP Address:172.31.85.225, IP Address:127.0.0.1, DNS:localhost

The admin password signs in over HTTPS (gRPC status 0) and reads the profile, while a call without a token, a wrong password and admin/admin are all refused (gRPC status 16, Unauthenticated):

sudo chirpstack-cloudimg-cred-roundtrip
CHIRPSTACK_LOGIN_OK https 0 0 16 16 16

Sign in is limited to 10 attempts a minute per client address (after a burst of 10); further attempts get HTTP 429 until the minute passes.

Step 7: Use the API

ChirpStack's API is gRPC. On this image it is served on port 443 only: gRPC-web for the console and browsers, and native gRPC (HTTP/2) for API clients and the ChirpStack SDKs, both with the instance's certificate. Create an API key in the console (Network Server > API Keys for an admin key, or Tenant > API Keys for a tenant key; copy it when it is shown, it is not shown again) and send it as authorization: Bearer <key>. With grpcurl from your own machine:

grpcurl -insecure -H 'authorization: Bearer <api-key>' -d '{"limit": 10}' \
  <chirpstack-host>:443 api.TenantService/List

Use -cacert chirpstack.crt (the certificate from /etc/nginx/ssl/chirpstack.crt) instead of -insecure until you install a certificate your clients trust (Step 12). Without a valid key, every API method answers Unauthenticated. API keys and console tokens are signed with this instance's api.secret; keep API keys secret, and delete the ones you no longer need.

Step 8: MQTT for your application

ChirpStack publishes every device event (up, join, ack, txack, log, status, location, integration) to the local broker under application/<application id>/device/<DevEUI>/event/<type>. Your application on this instance reads them as the integration account, whose password is MQTT_INTEGRATION_PASSWORD in the credentials file. Keep the password off the command line: put it in a mosquitto_sub options file only you can read:

install -d -m 0700 ~/.config
sudo sed -n 's/^MQTT_INTEGRATION_PASSWORD=/-P /p' /root/chirpstack-credentials.txt > ~/.config/mosquitto_sub
chmod 0600 ~/.config/mosquitto_sub
printf -- '-u integration\n' >> ~/.config/mosquitto_sub
mosquitto_sub -h 127.0.0.1 -p 1883 -t 'application/#' -v

Each device event arrives as one JSON line, for example after a device joins:

application/603cf866-a8a3-41dc-9c86-1a44e3eea09f/device/200b98f24d7d0807/event/join {"deduplicationId":"...","time":"...","deviceInfo":{...},"devAddr":"01b3e3be",...}

The same account may queue a downlink by publishing to application/<application id>/device/<DevEUI>/command/down. It cannot subscribe to or publish on the gateway topics, and anonymous clients are refused:

mosquitto_sub -h 127.0.0.1 -p 1883 -t '#' -C 1 -W 3
Connection error: Connection Refused: not authorised.

The broker listens on 127.0.0.1 only. To deliver events off the instance, use one of ChirpStack's integrations (HTTP, AWS SNS, Kafka, InfluxDB and others, per application under Integrations), or run a bridge on the instance; do not open 1883 in the security group.

Step 9: Connect your gateways

Point each gateway's Semtech UDP packet forwarder (global_conf.json / local_conf.json, gateway_conf) at the instance: server_address = the instance's DNS name or Elastic IP, serv_port_up and serv_port_down = 1700. Add the gateway in the console with its Gateway ID (Step 5); it turns Online with its first status packet.

The UDP packet forwarder has no authentication or encryption. Anyone who can send UDP to port 1700 can pretend to be a gateway (inject uplinks, receive downlinks meant for that gateway ID). The image keeps the bridge because nearly every gateway speaks this protocol, but open UDP 1700 in the security group only to your gateways' public addresses, or reach the instance over a VPN. For an authenticated connection, use LoRa Basics Station instead: gateways connect over TLS with client certificates. Set the bridge's backend to Basics Station in /etc/chirpstack-gateway-bridge/chirpstack-gateway-bridge.toml ([backend] type="basic_station", with bind, tls_cert, tls_key and ca_cert under [backend.basic_station], as in ChirpStack's Gateway Bridge documentation), restart chirpstack-gateway-bridge, open its TCP port (for example 3001) to your gateways and close UDP 1700.

The bridge serves one LoRaWAN region at a time; the image starts on EU868. ChirpStack has these regions enabled:

sudo grep -A14 'enabled_regions' /etc/chirpstack/chirpstack.toml
  enabled_regions = [
    "as923",
    "as923_2",
    "as923_3",
    "as923_4",
    "au915_0",
    "cn470_10",
    "cn779",
    "eu433",
    "eu868",
    "in865",
    "ism2400",
    "kr920",
    "ru864",
    "us915_0",

To serve another region, point the bridge at it (this rewrites its MQTT topic prefix and restarts it), and choose the same region in your device profiles:

sudo chirpstack-cloudimg-set-gateway-region us915_0
chirpstack-cloudimg-set-gateway-region: the gateway bridge now serves region us915_0

A region ChirpStack has not enabled is refused, and nothing changes.

Step 10: Your data

ChirpStack keeps tenants, users, gateways, devices, keys and frame metadata in the PostgreSQL database chirpstack; device sessions and the downlink queue state live in Valkey (in memory only, rebuilt from traffic). Back up the database, for example:

sudo -u postgres pg_dump -Fc chirpstack > chirpstack-$(date +%F).dump

Keep backups private and encrypted: they hold your devices' keys. Restore with pg_restore into a fresh chirpstack database owned by the chirpstack role, with ChirpStack stopped.

Step 11: Integrations and private endpoints

HTTP and other integrations send from ChirpStack to the URL you enter. By default ChirpStack reaches loopback and public IPv4 endpoints only. Denied are the private IPv4 ranges (10/8, 172.16/12, 192.168/16), 100.64/10, 0/8, link-local including 169.254.169.254, all IPv6 (which includes the NAT64, 6to4 and Teredo forms that carry an IPv4 address inside an IPv6 one), and your VPC's own IPv4 CIDR blocks, which chirpstack-cloudimg-egress-refresh reads from the instance metadata service into an nftables set for the chirpstack user at boot, before every ChirpStack start and hourly, so a VPC on a non-RFC 1918 range, or a CIDR you associate later, is covered too. That includes the instance's own private address: loopback is allowed by destination only (127.0.0.0/8 and ::1), so an integration cannot reach a local service such as SSH or nginx through it. A URL on a denied address fails with a connection error or a timeout in ChirpStack's log, so a tenant user cannot use an integration to read the instance metadata service or probe your network. The fixed ranges are systemd IPAddressDeny / IPAddressAllow settings on chirpstack.service (an allow entry wins over every deny entry); the VPC CIDRs are the nftables table inet cloudimg_chirpstack_egress.

To send to one endpoint inside your VPC (for example an internal load balancer or a VPC endpoint), allow just that address in an override drop-in and restart ChirpStack:

sudo install -d /etc/systemd/system/chirpstack.service.d
printf '[Service]\nIPAddressAllow=<private-ip-of-your-endpoint>/32\n' | sudo tee /etc/systemd/system/chirpstack.service.d/70-my-endpoint.conf
sudo systemctl daemon-reload && sudo systemctl restart chirpstack

Never allow 169.254.169.254 or fd00:ec2::254 (the metadata service), and never allow a whole range such as ::/0 or 2000::/3: because an allow wins, that would also allow the metadata service and your VPC.

IPv6 integration endpoints. To let integrations reach IPv6 endpoints, replace the deny list instead of adding an allow. This drop-in resets IPAddressDeny and lists again everything cloudimg denies except ::/0, and you must add your VPC's IPv6 CIDR (VPC console, IPv6 CIDRs) to it, along with any other IPv6 prefix you route privately:

D=/etc/systemd/system/chirpstack.service.d
{ printf '[Service]\nIPAddressDeny=\n'
  grep -h '^IPAddressDeny=' "$D/50-cloudimg-imds-deny.conf"
  sed -n 's#^\(IPAddressDeny=.*\) ::/0$#\1#p' "$D/60-cloudimg-integration-egress.conf"
  printf 'IPAddressDeny=%s\n' '<your-vpc-ipv6-cidr>'; } | sudo tee "$D/70-allow-ipv6.conf"
sudo systemctl daemon-reload && sudo systemctl restart chirpstack
systemctl show -p IPAddressDeny chirpstack

The last command should list your VPC's IPv6 CIDR and no ::/0. Delete 70-allow-ipv6.conf (then daemon-reload and restart) to go back to IPv4 only.

If the VPC lookup fails, integrations stop (fail closed). When the refresh cannot read a valid VPC CIDR list from the instance metadata service (5 tries over about a minute), it blocks all non-loopback traffic of the chirpstack user instead, so no integration can reach your VPC by accident. ChirpStack itself, the console, the API, gateways, MQTT, Valkey and PostgreSQL keep working; only integrations to outside endpoints fail. The refresh logs the failure, writes /run/cloudimg/chirpstack-egress-locked and retries every 5 minutes. Check it and re-run it once the metadata service is reachable (IMDSv2 on, hop limit at least 1):

cat /run/cloudimg/chirpstack-egress-locked
journalctl -u chirpstack-cloudimg-egress-refresh -n 20
sudo systemctl start chirpstack-cloudimg-egress-refresh

The refresh also restores the table if you load your own nftables rules with flush ruleset; run it after doing so.

Step 12: Your own DNS name and certificate

Point a DNS record at the instance, then set the name ChirpStack is served as. This mints a new self signed certificate for the name, pins it in nginx (the instance's IP addresses redirect to it) and records it in the credentials file:

sudo chirpstack-cloudimg-set-hostname <your-domain>

To use a certificate from your own certificate authority instead, copy it over the generated pair and reload nginx (running chirpstack-cloudimg-set-hostname again replaces it with a self signed one):

sudo install -m 0644 <your-domain>.crt /etc/nginx/ssl/chirpstack.crt
sudo install -m 0600 <your-domain>.key /etc/nginx/ssl/chirpstack.key
sudo nginx -t && sudo systemctl reload nginx

Behind a load balancer: terminate TLS on it and forward to port 443 on the instance with the configured host name (HTTP/2 to the target is needed for native gRPC). nginx always uses the connection's address as the client address, so every request through the load balancer counts against its sign-in budget; raise the limit in /etc/nginx/conf.d/cloudimg-chirpstack-guard.conf if that is too tight.

Step 13: Security checks

ChirpStack, the Gateway Bridge, Mosquitto, nginx, Valkey and PostgreSQL are blocked from the instance metadata service (systemd may list the addresses in a different order):

systemctl show -p IPAddressDeny chirpstack-gateway-bridge mosquitto nginx valkey-server postgresql@16-main
IPAddressDeny=::ffff:169.254.169.254/128 fd00:ec2::254/128 169.254.169.254/32 168.63.129.16/32

IPAddressDeny=::ffff:169.254.169.254/128 fd00:ec2::254/128 169.254.169.254/32 168.63.129.16/32

IPAddressDeny=::ffff:169.254.169.254/128 fd00:ec2::254/128 169.254.169.254/32 168.63.129.16/32

IPAddressDeny=::ffff:169.254.169.254/128 fd00:ec2::254/128 169.254.169.254/32 168.63.129.16/32

IPAddressDeny=::ffff:169.254.169.254/128 fd00:ec2::254/128 169.254.169.254/32 168.63.129.16/32

ChirpStack itself may reach loopback and public IPv4 only (Step 11): every private, link-local and carrier-grade NAT IPv4 range and all IPv6 (::/0) are denied, your VPC's IPv4 CIDR is in the nftables set, and loopback is the only allow. systemd folds ranges inside a larger one (the IPv6 entries into ::/0, a 172.31.0.0/16 default VPC into 172.16.0.0/12) and may list them in a different order:

systemctl show -p IPAddressAllow -p IPAddressDeny chirpstack
sudo nft list set inet cloudimg_chirpstack_egress vpc4
ls /run/cloudimg/chirpstack-egress-locked 2>/dev/null || echo '(not locked)'
IPAddressAllow=127.0.0.0/8 ::1/128
IPAddressDeny=168.63.129.16/32 169.254.0.0/16 100.64.0.0/10 172.16.0.0/12 10.0.0.0/8 0.0.0.0/8 ::/0 192.168.0.0/16
table inet cloudimg_chirpstack_egress {
    set vpc4 {
        type ipv4_addr
        flags interval
        auto-merge
        elements = { 172.31.0.0/16 }
    }
}
(not locked)

systemd may list the IPAddressAllow and IPAddressDeny entries in another order on each boot. The vpc4 elements are your own VPC's IPv4 CIDR blocks (172.31.0.0/16 is the default VPC).

The credentials are readable by root only, the configuration by root and the one service that uses it, and the Valkey socket by Valkey and ChirpStack only; other users cannot open it, and PostgreSQL does not answer on the network:

sudo stat -c '%a %U:%G %n' /root/chirpstack-credentials.txt /etc/chirpstack /etc/chirpstack/chirpstack.toml /etc/chirpstack-cloudimg /etc/chirpstack-gateway-bridge/chirpstack-gateway-bridge.toml /etc/mosquitto/cloudimg-passwd /etc/mosquitto/cloudimg-acl /run/valkey/valkey.sock
valkey-cli -s /run/valkey/valkey.sock PING || echo '(refused, as expected)'
pg_isready -h 127.0.0.1 -p 5432 || echo '(no network listener, as expected)'
600 root:root /root/chirpstack-credentials.txt
750 root:chirpstack /etc/chirpstack
640 root:chirpstack /etc/chirpstack/chirpstack.toml
700 root:root /etc/chirpstack-cloudimg
640 root:gatewaybridge /etc/chirpstack-gateway-bridge/chirpstack-gateway-bridge.toml
640 root:mosquitto /etc/mosquitto/cloudimg-passwd
640 root:mosquitto /etc/mosquitto/cloudimg-acl
770 valkey:chirpstack /run/valkey/valkey.sock
Could not connect to Valkey at /run/valkey/valkey.sock: Permission denied
(refused, as expected)
127.0.0.1:5432 - no response
(no network listener, as expected)

The Mosquitto password file holds only salted PBKDF2 hashes of the three account passwords, and the Valkey account file only a SHA-256 hash of ChirpStack's.

Step 14: Passwords

chirpstack-cloudimg-set-password gives a ChirpStack user (by default admin) a new random password with ChirpStack's own set-password and, for admin, records it in /root/chirpstack-credentials.txt. Run it with a password on standard input to choose your own (12 to 96 letters and digits):

sudo chirpstack-cloudimg-set-password
printf '%s\n' '<new-password>' | sudo chirpstack-cloudimg-set-password admin --stdin
chirpstack-cloudimg-set-password: the password of 'admin' was changed

A password change does not end console sessions that already hold a token: tokens are signed with api.secret and expire on their own. To invalidate every token and API key at once, replace secret under [api] in /etc/chirpstack/chirpstack.toml with a new random value (openssl rand -hex 32) and restart ChirpStack; then create new API keys.

Step 15: Server components

Component Path
ChirpStack settings (database, Valkey and MQTT accounts, api.secret; root:chirpstack 0640) /etc/chirpstack/chirpstack.toml, /etc/chirpstack/region_*.toml
Gateway Bridge settings (root:gatewaybridge 0640) /etc/chirpstack-gateway-bridge/chirpstack-gateway-bridge.toml
ChirpStack and bridge service hardening /etc/systemd/system/chirpstack.service.d/, /etc/systemd/system/chirpstack-gateway-bridge.service.d/
Mosquitto settings, topic rights and password hashes /etc/mosquitto/conf.d/cloudimg-chirpstack.conf, /etc/mosquitto/cloudimg-acl, /etc/mosquitto/cloudimg-passwd
Valkey settings (included last from valkey.conf) and account file /etc/valkey/cloudimg-chirpstack.conf, /etc/valkey/cloudimg-chirpstack-users.acl
Valkey account password (root only) /etc/chirpstack-cloudimg/
Database (PostgreSQL 16 cluster main) /var/lib/postgresql/16/main
nginx site, sign-in limit and gRPC snippets /etc/nginx/sites-available/cloudimg-chirpstack, /etc/nginx/conf.d/cloudimg-chirpstack-guard.conf, /etc/nginx/snippets/cloudimg-chirpstack-*.conf
Certificate and key /etc/nginx/ssl/
First boot and helpers /usr/local/sbin/chirpstack-firstboot, chirpstack-cloudimg-set-hostname, chirpstack-cloudimg-set-password, chirpstack-cloudimg-set-gateway-region, chirpstack-cloudimg-cred-roundtrip
Image notes /usr/share/doc/chirpstack-cloudimg/CLOUDIMG.txt
Credentials /root/chirpstack-credentials.txt (mode 0600)

Step 16: Updating

ChirpStack and the Gateway Bridge come from ChirpStack's apt repository (/etc/apt/sources.list.d/chirpstack.list, key pinned in /etc/apt/keyrings/chirpstack.gpg), Mosquitto from the Eclipse Mosquitto maintainers' archive (mosquitto-ppa.list, limited by /etc/apt/preferences.d/cloudimg-mosquitto-ppa to the three Mosquitto packages), and the rest from Ubuntu. Unattended upgrades apply Ubuntu's security updates; update ChirpStack and Mosquitto yourself after a database backup (Step 10), reading ChirpStack's release notes first:

sudo apt-get update && sudo apt-get install --only-upgrade chirpstack chirpstack-gateway-bridge mosquitto mosquitto-clients libmosquitto1

ChirpStack runs its database migrations when it starts. The cloudimg settings live in drop-ins under /etc/systemd/system/*.service.d/ and in /etc/chirpstack, so a package update keeps them.

Step 17: Security recommendations

  • Allow port 22 from your own networks only, 443 only from the networks your staff and applications use, and UDP 1700 only from your gateways (or use Basics Station, Step 9).
  • Keep the admin password secret (Step 14), give each member of staff their own account with the least rights they need, and create tenant API keys rather than admin keys for applications.
  • Use your own DNS name and a certificate your clients trust (Step 12).
  • Back up the database regularly, encrypted (Step 10).
  • Keep ChirpStack, Mosquitto and the operating system updated (Step 16).

Support and licensing

ChirpStack and the ChirpStack Gateway Bridge are free software under the MIT licence (ChirpStack's licence text is in /usr/share/doc/chirpstack/copyright); Mosquitto (EPL-2.0/EDL-1.0), PostgreSQL, Valkey and nginx carry their own open source licences. There is no per device or per gateway licence fee. This image is built and supported by cloudimg; ChirpStack is developed by Orne Brocaar and the ChirpStack contributors, and cloudimg is not affiliated with them.

  • Email: support@cloudimg.co.uk