Resolving LMQTT_Client Error 80C5: SIMATIC MQTT to Azure IoT Hub

David Krause16 min read
Industrial NetworkingSiemensTroubleshooting
Licensed PE Working through this on a live machine? A Maine-licensed engineer can take it from here — included with IMD hardware, by the hour for everything else. Book an engineer

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.

Symptom recap: LMQTT_Client block status = 16#8061, sub-status = 16#80C5. TCP/TLS handshake completes (port 8883 is reachable, root CA is trusted). Azure IoT Hub does not register a device session. No data-plane events appear on the device twin or telemetry pipeline.

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:

  1. 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.
  2. Expired SAS token — the default az iot hub generate-sas-token and 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 as 80C5.
  3. 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.
  4. Wrong device identity — the deviceId in the username must match the SAS token's sr= claim and the device must be enabled in the IoT Hub identity registry.
  5. 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.
A 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 deviceId used 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:

  1. Resource URI must be <hubname>.azure-devices.net/devices/<deviceid> — note the /devices/ segment. The sr= query parameter of the SAS token must be URL-encoded in the same way.
  2. 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.
  3. Token placement — insert the resulting string into the password field of the LMQTT_Client connection DB. The MQTT username field is the resource URI itself, not the token.
The default 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 sUserName field must include the api-version=2018-06-30 query string. Azure IoT Hub rejects MQTT CONNECT packets without it.
  • The sClientId is the same value as <device-id>; using a different value is silently dropped on the broker side and surfaces as 80C5.
  • The bUseTLS flag must be TRUE; a plain TCP connection to port 8883 is refused at the TCP level by the IoT Hub gateway.
  • The sTLSServerCertCN should equal the broker hostname. If you reference the broker by IP, set sTLSServerCertCN to 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:

  1. Download the certificate (PEM or DER) from the official DigiCert repository.
  2. Open the PLC device view in TIA Portal.
  3. Right-click the CPU and choose Properties → Security → Certificate manager.
  4. Click Add and select the root certificate.
  5. Assign it as a trusted root certification authority (not as a device certificate).
  6. Right-click the CPU in the project tree and choose Download → Hardware configuration to push the trust store to the runtime.
The Download → Hardware configuration step is critical. Importing the certificate in the offline project is not enough — the runtime certificate store is not updated until HW-config is reloaded. This is the exact pitfall flagged in the Siemens application example for MQTT, and it is one of the most common reasons a connection that should be working still fails on the bench.

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:

  1. 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.
  2. 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 with 80C5 even though the configuration is unchanged, because the IP changed.
  3. 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:

  1. 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.
  2. Block status — bring the LMQTT_Client block into RUN. The status word should transition from 7002 (CONNECTING) to 7000 (CONNECTED) within a few seconds. A persistent 80C5 after the TLS handshake indicates the SAS token is still rejected.
  3. 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.
  4. 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.
  5. 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:

  1. 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.
  2. Upload the root CA certificate to the IoT Hub under Certificates.
  3. Import the device certificate and private key into the CPU certificate store.
  4. Switch the LMQTT_Client connection DB to X.509 mode (disable SAS password, enable client cert fields).
  5. Re-download HW-config and the connection DB.
  6. 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.

Back to blog