Writing / Build Log
One modem, two TLS stacks, and only one of them works
A cellular broker connection returned "not authorized" with correct credentials. The cause was SNI — and the AT command to enable it is not the one the manual implies. Later, the same modem refused every HTTPS handshake while MQTTS kept working fine. Both had the same shape.
AT+CMQTTCONNECT came back with:
+CMQTTCONNECT: 0,3131 is "not authorized". The username and password were correct — the same credentials connected fine from a laptop with paho-mqtt. The modem was on the network, the PDP context was up, DNS resolved. Everything worked except the part that mattered.
Part 1 — the auth failure that was not an auth failure
The broker is a multi-tenant cloud service. Many customers sit behind one set of front-end addresses, and the TLS layer is what tells them apart: the client announces the hostname it wants during the handshake, via SNI — Server Name Indication — and the front end routes to that tenant.
Without SNI, the handshake still completes. You reach *a* broker. Just not yours. Your credentials are then checked against the wrong tenant and come back as 31, not authorized.
That is a nasty failure mode, because every layer reports success right up until the one that reports a lie. The error blames your credentials. The problem is in the TLS layer underneath, one level below where the error surfaces.
So: enable SNI. The obvious command:
AT+CSSLCFG="sni",0,"<hostname>"ERROR.
On this module — A7670C-LANS, firmware V11.0.01 — that parameter does not exist. Not "not supported": the name itself is wrong. Which sends you back through the SSL application note hunting for a parameter that is not documented under any name you would guess.
The answer was not in the manual. It was in source code — line 184 of TinyGsmMqttA76xx.h, in a vendor's *forked* TinyGSM:
AT+CSSLCFG="enableSNI",0,1enableSNI, a boolean, not sni with a hostname. The modem already knows the hostname from the connect string; the flag just tells it to send it.
Two other commands were wrong for the same reason — the intuitive spelling is not the working one:
Intuitive Actually works Why
------------------------- -------------------------------- -------------------------------------------
AT+CSSLCFG="sni",0,"host" AT+CSSLCFG="enableSNI",0,1 Boolean toggle, not a hostname parameter
AT+CFSWFILE AT+CCERTDOWN="ca_cert.pem",<len> Different filesystem layer for certificates
MQTT 3.1 (the default) AT+CMQTTCFG="version",0,4 This broker expects 3.1.1 And one genuinely counter-intuitive detail: the connect URL uses tcp:// even though the connection is TLS. TLS is selected by ssl=1 in AT+CMQTTACCQ, and passing ssl:// there is rejected outright.
The working sequence, in order:
AT+NETOPEN
AT+CCERTDOWN="ca_cert.pem",<len> ← wait for '>' then send the root CA PEM
AT+CCERTLIST ← verify it landed
AT+CSSLCFG="sslversion",0,4
AT+CSSLCFG="authmode",0,1
AT+CSSLCFG="cacert",0,"ca_cert.pem"
AT+CSSLCFG="enableSNI",0,1 ← the one that mattered
AT+CMQTTSTART
AT+CMQTTACCQ=0,"<client_id>",1 ← ssl=1
AT+CMQTTSSLCFG=0,0 ← context 0; context 1 is rejected for MQTT
AT+CMQTTCFG="version",0,4
AT+CMQTTCONNECT=0,"tcp://<broker>:8883",60,1,"<user>","<pass>"
→ +CMQTTCONNECT: 0,0Part 2 — the same modem, a different answer
Later the project needed to pull a large file over HTTPS. Same modem, same PDP context, same certificate, MQTTS solid and handshaking correctly by then.
Every attempt returned:
+HTTPACTION: 0,715,0715 is handshake failed. Which made no sense, because handshakes were demonstrably working a layer over.
Rather than guess, I ran a matrix, changing one variable at a time:
Config Result
------------------------------------------------------ ------
ctx 1, authmode=0, SSLCFG bind unquoted 715
ctx 1, authmode=0, SSLCFG bind quoted "1" 715
ctx 0, authmode=0 — verification fully off 715
A second, unrelated host — different CDN, different CA 715
ctx 0, sslversion=3 — TLS 1.2 forced 715
ctx 0, TLS 1.2, enableSNI=0 715
The same host over plain HTTP 200 Every hypothesis died:
- Not the CA. It fails with
authmode=0— certificate verification switched off entirely. The handshake dies before any certificate is evaluated. There *was* a real root-CA mismatch elsewhere in the project, and it was not the cause here. - Not one provider's edge. An unrelated host on different infrastructure fails identically.
- Not the TLS version. Forcing TLS 1.2 changes nothing.
- Not SNI — the Part 1 villain. Fails with
enableSNIat both 0 and 1. - Not the SSL context or the bind syntax. Both quoted and unquoted
AT+HTTPPARA="SSLCFG"are accepted withOK, contrary to what I had assumed earlier. - Not the bearer or APN. Plain HTTP over the same context moves bulk data without complaint.
Which leaves one explanation, and it is the thing I actually took away from this project:
There are two independent TLS clients inside the modem firmware. AT+CMQTT* has its own, and it completes handshakes correctly. AT+HTTP* has a different one, and on this firmware build it cannot complete a handshake with anything. "The modem does TLS" was never a single fact — it was two facts that happened to agree until they didn't.
The vendor's own update notes list "Known HTTP 715 Errors" against specific firmware builds and mark them fixed in later images — but publish no image for this module variant, and warn that flashing the wrong one can brick it. So it is not patchable from the bench.
Working around it is a separate design problem, and one whose details belong in the device rather than in a blog post. The short version: if a transport you assumed was available turns out not to exist, the question becomes which property you were actually relying on it for. Confidentiality and integrity are not the same requirement, they fail differently, and they are not always best solved in the same layer.
Two bench gotchas that cost real time
Neither is about TLS. Both wasted hours.
Plugging the modem into a laptop kills your WiFi. The A7670C exposes an RNDIS interface. NetworkManager sees a new wired device, auto-creates a profile, DHCPs an address, and logs policy: set 'Wired connection 2' as default for IPv4 routing and DNS. Your actual internet connection is now second choice. This happens at USB enumeration — before a single AT command. The fix, no sudo required:
nmcli connection add type ethernet con-name a7670c-modem \
802-3-ethernet.mac-address <modem-mac> \
ipv4.method auto ipv4.never-default yes ipv6.never-default yes \
ipv4.ignore-auto-dns yes ipv4.route-metric 9999ModemManager fights you for the AT port. It claims a ttyUSB node and probes it on its own schedule. If an MCU is driving the same modem over UART at the same time, two AT sessions interleave and both produce garbage. Unplug USB before running the device — the symptom looks like random modem flakiness, not a conflict.
What I would tell myself at the start
- An error code names the layer that noticed, not the layer that broke.
31, not authorizedwas a TLS routing problem wearing an auth problem's clothes. - When a documented parameter returns ERROR, read the library that already works. The answer was in a vendor fork's header file, not in either application note.
- Change one variable at a time and write down the result. The 715 matrix looks tedious. It is what turned "TLS is broken somehow" into "the HTTP stack's TLS client specifically is broken", which is a fact you can design around.
- A subsystem working is not the subsystem next to it working. Same modem, same context, same certificate, opposite outcome.