Resolving LMQTT_Client Error 80C5: SIMATIC MQTT to Azure IoT Hub
When a SIMATIC S7-1500 (or compatible ET 200SP CPU) uses the LMQTT_Client function block from the Siemens "Libraries for Communication for SIMATIC Controllers" to connect to an Azure IoT Hub device endpoint, the call can complete with block status 16#8061 and a sub-status of 16#80C5. From the engineering seat this is the moment the MQTT CONNECT packet returns CONNACK with a non-success return code and the SIMATIC stack aborts the TLS session. The combination is a server-side reject, almost always driven by an authentication failure in the SAS token supplied as the MQTT password field.
This reference documents the failure mode, the underlying MQTT 3.1.1 CONNACK semantics, and the field-proven procedure to bring the connection up using PowerShell-generated SAS tokens, the DigiCert root CA store, and a working DNS or IP-literal broker address.
Affected Components and Versions
| Component | Recommended Version / Configuration |
|---|---|
| TIA Portal | V17 Update 2 or later; V18 recommended for LMQTT maintenance fixes |
| LMQTT_Client library | v1.0.x or v1.1.x (TIA Portal V17 / V18 distribution) |
| SIMATIC CPU | S7-1500 CPU firmware 2.9.x or newer; ET 200SP CPU 1515SP PC2 with V2.9 firmware |
| MQTT protocol | MQTT 3.1.1 (Azure IoT Hub does not accept MQTT 5.0 on the device endpoint) |
| TLS | TLS 1.2 (TLS 1.0 and 1.1 are rejected by the Azure IoT Hub gateway) |
| Port | 8883 (MQTT over TLS). AMQP uses 5671 and is not interchangeable for LMQTT_Client. |
| Root CA | DigiCert Baltimore CyberTrust Root (legacy) or DigiCert Global Root G2 (current) |
| Azure IoT Hub SKU | S1, S2, S3, or B1/B2/B3 (Free tier supports MQTT but with strict throttling) |
The LMQTT_Client function block is part of the SIMATIC Communication Libraries, documented in the manual Libraries for Communication for SIMATIC Controllers on Siemens Industry Online Support. The companion application example Use the SIMATIC controller as an MQTT client walks through both a generic broker and the Azure IoT Hub path, and explicitly flags the HW-config reload after certificate import.
Root Cause Analysis
The 8061/80C5 combination is a SIMATIC-side encoding of an MQTT CONNACK with return code != 0. The MQTT 3.1.1 specification defines five return codes for CONNACK:
| Return Code | Meaning |
|---|---|
| 0 | Connection Accepted |
| 1 | Refused: unacceptable protocol version |
| 2 | Refused: identifier rejected |
| 3 | Refused: server unavailable |
| 4 | Refused: bad user name or password |
| 5 | Refused: not authorized |
Azure IoT Hub maps server-side authentication, authorization, and SAS token validation failures onto CONNACK return code 4 or 5. The 80C5 sub-status within LMQTT_Client corresponds to "Connection refused: not authorized" — i.e. the broker accepted the protocol version, accepted the client identifier, but rejected the credentials or the device identity.
The most common upstream causes, in order of frequency in field reports, are:
- Malformed SAS token — the token was hand-assembled, truncated, base64-decoded when it should not have been, or built with the wrong resource URI. The original case closed only after switching to a PowerShell-generated token.
-
Expired SAS token — the default
az iot hub generate-sas-tokenand the PowerShell helper in Microsoft's documentation emit a token that expires in one hour. If the device is commissioned and the connection only runs hours later, the SAS token has already expired and the broker returns a 401 to the gateway, which the SIMATIC stack surfaces as80C5. -
Wrong username format — the MQTT username for Azure IoT Hub must be exactly
{iothubhostname}/{deviceid}/?api-version=2018-06-30. Using{deviceid}alone, or omitting the API version, is rejected. -
Wrong device identity — the
deviceIdin the username must match the SAS token'ssr=claim and the device must be enabled in the IoT Hub identity registry. - Policy key vs device key confusion — the IoT Hub service policy keys cannot be used to mint a device-level SAS token. The token must be signed with the device's primary or secondary key.
80C5 sub-status is not the same as a TLS handshake failure. If the SIMATIC stack returned a different sub-status (for example in the 80C0–80C1 range for certificate issues), the problem would be in the trust chain. The 80C5 specifically indicates a server-side CONNACK reject after a clean TLS handshake.Pre-flight Checklist
Before changing the connection DB, confirm the following items are correct. Each of these is responsible for at least one reported MQTT connection failure mode against Azure IoT Hub:
- Azure IoT Hub hostname is reachable as
<your-hub-name>.azure-devices.net. - A device is registered in the IoT Hub identity registry with the same
deviceIdused in the SIMATIC connection DB. - The device is enabled (not disabled) in the Azure portal.
- The IoT Hub has not been soft-deleted or moved to a region with restricted access.
- The outbound firewall on the PLC network allows TCP 8883 to the IoT Hub FQDN.
- The PLC has a system time within ±5 minutes of UTC; large clock skew invalidates the TLS chain.
- The DigiCert Baltimore CyberTrust Root (or Global Root G2) is loaded in the CPU certificate store.
- The DNS resolver is configured in the PLC's PROFINET interface or the broker address is supplied as an IP literal.
Solution: Generate the SAS Token with PowerShell
The Azure IoT Hub documentation provides several methods to mint a SAS token. The two methods that produce a token the SIMATIC stack accepts as a password are Azure CLI and the PowerShell helper published in the official Microsoft docs. The original case closed only after switching to the PowerShell path, which is reproduced below.
Method A: Azure CLI (preferred for engineers with az installed)
az iot hub generate-sas-token \
--hub-name <your-hub-name> \
--device-id <device-id> \
--key-type primary \
--duration 3600
The --duration parameter controls the validity window in seconds. The default 3600 (one hour) is fine for commissioning, but a production deployment should request 31536000 (one year) or a value that matches the maintenance window, and store the rotated token off-line. See the az iot hub generate-sas-token reference for the full parameter set.
Method B: PowerShell helper from the official Microsoft documentation
The original case was resolved using the PowerShell function below, which mirrors the helper in the Microsoft IoT Hub SAS token developer guide.
function New-SASToken {
param (
[string]$resourceUri = '',
[string]$key = '',
[string]$keyName = 'primary',
[int]$ttl = 3600
)
# Add System.Web for HttpUtility
Add-Type -AssemblyName System.Web
$expiry = [Math]::Round(
(New-TimeSpan -Start (Get-Date).ToUniversalTime() `
-End (Get-Date).ToUniversalTime().AddSeconds($ttl)
).TotalSeconds
)
$stringToSign = [System.Web.HttpUtility]::UrlEncode($resourceUri) + "\n" + $expiry
$bytesToSign = [Text.Encoding]::UTF8.GetBytes($stringToSign)
$keyBytes = [Convert]::FromBase64String($key)
$hmac = New-Object System.Security.Cryptography.HMACSHA256
$hmac.Key = $keyBytes
$hash = $hmac.ComputeHash($bytesToSign)
$signature = [Convert]::ToBase64String($hash)
$signature = [System.Web.HttpUtility]::UrlEncode($signature)
$sasToken = "SharedAccessSignature sr=$resourceUri&sig=$signature&se=$expiry&skn=$keyName"
return $sasToken
}
# Usage
$resource = "<your-hub-name>.azure-devices.net/devices/<device-id>"
$key = "<device-primary-key-as-base64>"
$token = New-SASToken -resourceUri $resource -key $key -ttl 86400
Write-Output $token
Three rules must be followed exactly:
-
Resource URI must be
<hubname>.azure-devices.net/devices/<deviceid>— note the/devices/segment. Thesr=query parameter of the SAS token must be URL-encoded in the same way. - Signing key must be the device's primary or secondary key (32-byte base64) copied verbatim from the IoT Hub portal. Service-shared-access-policy keys will be rejected.
-
Token placement — insert the resulting string into the
passwordfield of the LMQTT_Client connection DB. The MQTTusernamefield is the resource URI itself, not the token.
ttl of 3600 means the token is good for one hour. Once the PowerShell command is run and pasted, the operator typically has one hour to download the project to the CPU and bring the connection up. If the operator waits two hours to re-test, the very same string fails with 80C5 because the se= claim is in the past. Use 86400 for daily rotation or 31536000 for annual rotation in production.Configuring the Connection DB in TIA Portal
The LMQTT_Client connection DB is a UDT instance that holds the broker address, port, client ID, username, password, TLS settings, and the keep-alive interval. A typical DB has the following structure:
| Tag | Type | Value (Azure IoT Hub) |
|---|---|---|
connection.sHostName |
STRING[255] | <hubname>.azure-devices.net |
connection.nPort |
UINT | 8883 |
connection.sClientId |
STRING[64] | <device-id> |
connection.sUserName |
STRING[255] | <hubname>.azure-devices.net/<device-id>/?api-version=2018-06-30 |
connection.sPassword |
STRING[512] | The PowerShell-generated SAS token |
connection.nKeepAlive |
UINT | 60 (seconds) |
connection.bUseTLS |
BOOL | TRUE |
connection.sTLSServerCertCN |
STRING[255] | <hubname>.azure-devices.net |
connection.nQoS |
USINT | 0, 1, or 2 (Azure IoT Hub supports all three) |
Important points:
- The
sUserNamefield must include theapi-version=2018-06-30query string. Azure IoT Hub rejects MQTT CONNECT packets without it. - The
sClientIdis the same value as<device-id>; using a different value is silently dropped on the broker side and surfaces as80C5. - The
bUseTLSflag must beTRUE; a plain TCP connection to port 8883 is refused at the TCP level by the IoT Hub gateway. - The
sTLSServerCertCNshould equal the broker hostname. If you reference the broker by IP, setsTLSServerCertCNto the FQDN so the SIMATIC stack validates the SAN/CN against the certificate.
Sample SCL call sequence
// Acquire connection parameters from operator
IF bStart THEN
iotStatus := LMQTT_Client_Connect(
sHostName := sHubName,
nPort := 8883,
sClientId := sDeviceId,
sUserName := sUserNameFull, // includes /?api-version=2018-06-30
sPassword := sSASToken,
nKeepAlive := 60,
bUseTLS := TRUE
);
bStart := FALSE;
END_IF;
// Publish on rising edge
IF bPublishTrigger AND (iotStatus = 16#7000) THEN
iotPublish := LMQTT_Client_Publish(
sTopic := 'devices/<device-id>/messages/events/',
pPayload := ADR(payloadBuffer),
nLength := payloadLen,
nQoS := 1
);
bPublishTrigger := FALSE;
END_IF;
Importing the Root CA Certificate
Azure IoT Hub endpoints are signed by the DigiCert Baltimore CyberTrust Root (legacy) or the DigiCert Global Root G2 (current). The CPU's trust store must contain the matching root. The procedure in TIA Portal is:
- Download the certificate (PEM or DER) from the official DigiCert repository.
- Open the PLC device view in TIA Portal.
- Right-click the CPU and choose Properties → Security → Certificate manager.
- Click Add and select the root certificate.
- Assign it as a trusted root certification authority (not as a device certificate).
- Right-click the CPU in the project tree and choose Download → Hardware configuration to push the trust store to the runtime.
If the operator uses the IP address of the IoT Hub (which is the documented workaround when the SIMATIC CPU has no CP card and DNS is unavailable), the TLS server certificate is still checked against the CN/SAN of the certificate. The certificate is issued to <hubname>.azure-devices.net, so the sTLSServerCertCN must be the FQDN even when sHostName is the literal IP.
Resolving the DNS Issue
The SIMATIC CPU firmware on a non-CP device does not always present a configurable DNS resolver in the project interface. There are three workable approaches:
- Configure a static DNS server in the PROFINET interface properties of the CPU. Most S7-1500 CPUs accept a primary and secondary DNS server. Once configured, the FQDN resolves without code changes.
-
Use the IP literal for
sHostName. This bypasses DNS but requires the engineer to know the IP block of the IoT Hub. The IP is not static — Azure assigns it from a regional pool, and it can change. A re-test after any Azure-side maintenance window can fail with80C5even though the configuration is unchanged, because the IP changed. - Add a static A-record resolution in the engineering station and rely on the engineering network's resolver. This is fragile in brownfield networks.
In production, the recommended path is option 1 with a corporate DNS server, and option 2 only as a temporary commissioning fallback. If the IP literal is the only viable path, document the IP in the maintenance runbook and build a watchdog that retries DNS resolution periodically.
Network and Firewall Configuration
The MQTT-over-TLS path to Azure IoT Hub requires:
| Direction | Protocol | Port | Source | Destination |
|---|---|---|---|---|
| Outbound | TCP | 8883 | PLC IP | IoT Hub public IP (resolved from <hubname>.azure-devices.net) |
| Outbound (optional) | TCP | 443 | PLC IP |
management.azure.com for diagnostic API |
The PLC's outbound traffic must be allowed by the network firewall, any NAT gateway, and any stateful inspection device in the path. If the corporate proxy performs TLS interception (SSL inspection), it will substitute the certificate chain and the SIMATIC stack will reject the connection. In that case, either exclude the IoT Hub FQDN from SSL inspection or use a direct path. For shared MQTT broker deployments that are not Azure IoT Hub, see the Azure Event Grid MQTT broker troubleshooting guide for parallel guidance on client metadata, certificates, and clean session flags.
Verifying the Connection
Once the SAS token, certificate store, and connection DB are configured, perform the following verification steps in order:
- Online view of the connection DB — confirm the password field is the SAS token generated by PowerShell. The value in the offline DB and the value in the online DB must match. If the password shows as an empty string online, the load was incomplete; re-download HW-config and the DB.
-
Block status — bring the LMQTT_Client block into RUN. The status word should transition from
7002(CONNECTING) to7000(CONNECTED) within a few seconds. A persistent80C5after the TLS handshake indicates the SAS token is still rejected. -
Azure portal device blade — open the IoT Hub → Devices →
<device-id>. The "Connection state" indicator should change to "Connected" within 30 seconds of the PLC's successful CONNECT. - Telemetry test — trigger a publish from the SIMATIC side using the LMQTT_Client publish call with a sample payload. Confirm reception in the Azure portal's "Device-to-cloud messages" blade or in the configured Event Hub.
- Cloud-to-device test — from the IoT Hub's "Cloud-to-device messages" blade, send a test message. Confirm the LMQTT_Client subscription callback receives it.
Troubleshooting Matrix
| Observed Symptom | Likely Cause | Action |
|---|---|---|
| Block status 8061, sub 80C5, TLS completes | SAS token malformed or expired | Regenerate with PowerShell or az iot hub generate-sas-token; check se= expiry |
| Block status 8061, sub-status in 80C0–80C1 range | Root CA not trusted | Import DigiCert Baltimore CyberTrust Root or Global Root G2; reload HW-config |
| Block status 8061, TCP does not connect | Firewall blocks 8883 or DNS fails | Verify outbound 8883; configure DNS or use IP literal |
| Block cycles between 7002 and 80C5 every few minutes | SAS TTL too short | Regenerate with TTL ≥ 86400; schedule rotation |
| Password field empty in online DB | Project download did not push the DB | Re-download HW-config and the connection DB; verify online |
| Connection works for 1 hour, then fails | Default TTL expired | Use --duration 86400 in CLI or -ttl 86400 in PowerShell |
| LMQTT_Client reports TLS alert from peer | TLS version or cipher mismatch | Confirm CPU firmware supports TLS 1.2; check corporate proxy for SSL interception |
| Azure portal shows "Connected" but telemetry not received | Topic filter not matched by IoT Hub routing | Verify topic is devices/<device-id>/messages/events/ or a registered custom endpoint |
Production Hardening
A production deployment should not rely on the operator manually rotating a SAS token every hour. Standard hardening steps:
- Set TTL to 86400 (one day) or longer when commissioning. A short TTL is fine for development, but a 1-hour TTL in a 24/7 line will fail at every hour boundary unless the token is regenerated and reloaded automatically.
- Use X.509 device authentication instead of SAS tokens where possible. Azure IoT Hub supports per-device X.509 client certificates with a CA chain. The LMQTT_Client library supports this mode via a separate configuration path and a client certificate loaded into the CPU certificate store. See the IoT Hub X.509 certificate documentation for the CA, device certificate, and fingerprint setup steps.
- Store the SAS token off-line — the password field in the offline DB is the canonical storage. Ensure the engineering station is on a controlled network and the project is source-controlled.
- Monitor the connection state — the LMQTT_Client block exposes a status tag that can be subscribed to in the PLC program and used to drive a reconnect sequence on a watchdog. A typical watchdog re-issues the connect call after a configurable cool-down if the status is non-zero for more than N seconds.
- Document the rotation procedure — the operator must know how to regenerate the SAS token, update the DB, and re-download the project. Include the PowerShell script in the maintenance runbook and version it with the project.
Migrating From SAS Tokens to X.509 (Optional)
For brownfield deployments that have outgrown SAS tokens, Azure IoT Hub supports X.509 client certificate authentication. The migration steps are:
- Generate a device certificate signed by an IoT Hub root or intermediate CA registered in the portal. See the IoT Hub X.509 security documentation for the full CA hierarchy requirements.
- Upload the root CA certificate to the IoT Hub under Certificates.
- Import the device certificate and private key into the CPU certificate store.
- Switch the LMQTT_Client connection DB to X.509 mode (disable SAS password, enable client cert fields).
- Re-download HW-config and the connection DB.
- Verify in the Azure portal device blade that the authentication method reports "X.509 CA Signed".
X.509 removes the SAS token expiry problem entirely and aligns with the broader Azure IoT security guidance in the IoT Hub SAS developer guide and IoT Hub MQTT support reference.
What does the LMQTT_Client 80C5 sub-status mean when the block status is 8061?
The 80C5 sub-status maps to MQTT CONNACK return code 4 or 5 — "bad user name or password" or "not authorized". The TLS handshake completed and the broker accepted the MQTT 3.1.1 protocol, but rejected the credentials. In practice this is almost always caused by a malformed or expired SAS token in the connection DB password field, a wrong deviceId in the username, or a disabled device in the IoT Hub identity registry.
How do I extend the SAS token validity beyond the default 1 hour?
Use the --duration parameter on az iot hub generate-sas-token or the -ttl parameter on the PowerShell New-SASToken helper. A typical production TTL is 86400 seconds (one day) for daily rotation, or 31536000 seconds (one year) for annual rotation. The token's se= claim is in Unix epoch seconds; verify it is in the future before downloading to the PLC.
Why does my SIMATIC CPU fail DNS resolution for the Azure IoT Hub hostname?
S7-1500 CPUs without a CP (communications processor) card have a limited DNS client and may report "DNS server not configured" if no resolver is set on the PROFINET interface. Configure a primary and secondary DNS server under the CPU's PROFINET properties, or use the IP literal of the IoT Hub as the broker address. The IP can change after Azure maintenance windows, so the DNS path is the long-term recommendation.
Do I need to reload HW-config after importing the DigiCert root CA certificate?
Yes. Adding the root CA in the TIA Portal certificate manager updates only the offline project. The runtime certificate store is updated only when the CPU's hardware configuration is reloaded via Download → Hardware configuration. Skipping this step leaves the trust store empty and the TLS handshake will fail with a certificate verification error rather than the 80C5 you are seeing now.
Which TCP port and root CA certificate does Azure IoT Hub MQTT require?
Azure IoT Hub accepts MQTT connections on TCP port 8883 with TLS 1.2. The server certificate chain is rooted at the DigiCert Baltimore CyberTrust Root (legacy) or the DigiCert Global Root G2 (current). The MQTT username must be <hubname>.azure-devices.net/<device-id>/?api-version=2018-06-30 and the password must be a valid SAS token signed with the device's primary or secondary key.