Edge Agent Troubleshooting
Most problems fall into one of a few buckets: the agent can't reach Haltless, readings are being rejected, or a source device isn't responding. Start with the logs: they name the cause in almost every case.
Reading the logs
When the agent runs under systemd, its logs go to the journal:
# Follow live
sudo journalctl -u haltless-agent -f
# Last 200 lines
sudo journalctl -u haltless-agent -n 200
# Since a point in time
sudo journalctl -u haltless-agent --since "1 hour ago"
If you run the agent in the foreground, the same messages print to the terminal. For more detail during diagnosis, set log_level: "DEBUG" in config.yaml and restart.
The key line to look for is the heartbeat: it reports the agent's connection status (active, or a reason it can't send) on a regular interval.
Connectivity and firewalls
Symptom: log messages about connection errors or timeouts; the agent reports it is buffering locally.
The agent needs only outbound HTTPS to api.haltless.io on port 443. Nothing needs to connect into the agent.
- Confirm the host can reach Haltless. A
2xxhere means the path is open:curl -sS -o /dev/null -w "%{http_code}\n" https://api.haltless.io/health - Check DNS resolves:
nslookup api.haltless.io. - Confirm your firewall allows outbound 443 to that host.
- If you route through a corporate proxy, make sure the agent's environment is configured to use it (for example, an
HTTPS_PROXYvalue in the service environment).
While Haltless is unreachable, the agent keeps collecting and writes readings to its local buffer. It automatically resumes sending, oldest data first, once the connection is restored, so a brief outage is invisible downstream.
The first send that finds no connection is saved to the buffer straight away; the agent does not stop collecting to retry it. It checks the connection again on the next cycle, then 30 seconds later, then less often while the outage lasts (at most every 5 minutes), so sending resumes within that interval of Haltless coming back. In the log you'll see send_batch no connection and buffered N readings locally when the connection drops, and Flushing N buffered batches once it returns. Buffered readings go out in requests of up to 1,000 readings (Flushed N buffered readings), so even a long outage takes few requests to catch up.
Buffering during outages
The agent is designed to ride out network interruptions. When it can't send, it persists readings to a crash-safe on-disk buffer (buffer.db in buffer_dir) and drains them in order when connectivity returns.
Two limits keep the buffer bounded, and both mean data loss only in a sustained outage:
- Size cap (
max_buffer_size_mb, default 512): once the buffer reaches this size while offline, the newest readings are dropped. - Retention (~24 hours): buffered readings older than about a day are evicted automatically.
If you expect long outages, raise max_buffer_size_mb and make sure buffer_dir has enough free disk.
Disk space for the buffer
Symptom: a growing buffer.db, or the agent warns it is dropping readings because the buffer is full.
A growing buffer almost always means the agent can't reach Haltless. Fix connectivity first (see above) and the buffer will drain on its own.
- Check the buffer size:
ls -lh /var/lib/haltless-agent/buffer.db(adjust for yourbuffer_dir). - Make sure
buffer_diris on a volume with enough free space for your worst-case offline window. - Prefer fast local storage (an SSD) for
buffer_dirso draining a large backlog is quick. - Keep
buffer_diron a normal data path such as/var/lib/haltless-agent. System directories are rejected at startup.
API-key and authentication errors
Symptom: the log reports API key refused (status=401) or (status=403) and says it is holding readings on disk, or the heartbeat reports heartbeat rejected.
A refused key is not the readings' fault, so the agent does not drop them. When Haltless refuses the key (401: wrong, expired or revoked; 403: it lacks the ingest scope), the agent stops sending and keeps collecting into its local buffer, as it does during an outage. It asks again at every heartbeat (every 5 minutes) and, once the key is accepted, sends the backlog oldest first. The readings wait within the buffer's limits (see Buffering during outages), so fix the key within about a day to lose nothing. To fix it:
- Confirm the
api_keyinconfig.yamlis correct and has not been revoked. - Check for stray whitespace or a truncated value. The key must be pasted in full.
- For a
403, give the key theingestscope. An edge-agent key created without scopes has it. See API keys. - If in doubt, create a fresh key and update the config.
- Restart the agent after changing the key in
config.yaml. What it buffered meanwhile is sent with the new key. A fix made in Haltless instead (a scope added, a key re-enabled) needs no restart: it takes effect at the next heartbeat.
A 403 on the heartbeat alone is different: heartbeat refused (403): the API key lacks the 'agent' scope means readings keep flowing, but Haltless does not record the agent's check-ins or version until the key gets the agent scope.
Collection paused (company suspended)
Symptom: the heartbeat reports a payment-required (402) status and the agent pauses collection.
This means your company's Haltless account has been suspended. The agent intentionally stops collecting so it doesn't pile up data that can't be delivered. Contact your Haltless administrator or Haltless support to have the account reinstated; the agent keeps checking in (about once an hour while paused) and automatically resumes, flushing anything it had buffered, once the account is active again. Nothing needs to change in the agent's configuration.
Rejected readings
If you see rejections, check the reason. Common ones are below. See the API error reference for the full list.
System identifier not found. The reading's System identifier doesn't match an active System in your company. Haltless rejects just that reading and stores the rest of the batch. Identifiers are case-sensitive; LINE-3 and line-3 are different Systems. Create the System on the portal's Systems page, or correct system_identifier in the source.
The next three make Haltless refuse the whole request (422). The agent then splits the batch to find the offending readings, drops and logs them, and sends the rest.
Value must be finite. The value was NaN or infinite. For Modbus, check the scale and offset: a bad conversion can produce a non-finite result. Also check the source device for fault states that emit non-numeric values.
Missing unit. Every reading needs a unit. Make sure your register/node config sets unit, or that your CSV column / JSON key for the unit is populated.
Timestamp out of range. The reading's timestamp is more than 5 minutes in the future or more than 3,650 days in the past. This is almost always a clock problem (see below).
Readings arrive but nothing is monitored. A tag the System has not sent before waits, unassigned, until someone assigns it to a machine on the System's page. Its readings are stored meanwhile and come along when it is assigned.
Timestamp and clock issues
Symptom: readings rejected for an out-of-range timestamp, or data appearing at the wrong time on the dashboard.
- Timestamps are handled in UTC. For the PLCs and controllers (Modbus TCP and RTU, OPC-UA, S7, EtherNet/IP, MELSEC, FINS, ADS, and BACnet/IP), the agent stamps readings with the host's current time, so keep the host clock synchronized with NTP.
- For MQTT, a message's own
timestampis used when it has one, so the publishing device's clock matters too. A message time more than two minutes ahead of the agent host's clock is replaced by the arrival time and logged once per tag. Sparkplug B works the same way with each metric's own timestamp. - For PI Web API, a value keeps the time PI recorded it. One ahead of the agent host's clock takes the agent's time, so check the PI server's clock too.
- For CSV and JSON, the timestamp comes from your export. If it has no timezone offset, the agent assumes UTC and logs a one-time warning, so make sure your exports really are UTC, or include an explicit offset.
- If readings land in the future or far in the past, check both the source device clock and the agent host clock.
The agent replays buffered readings on reconnect; re-sending the same reading is safe and does not create duplicates.
Rate limiting
Symptom: the log reports that a batch was rate-limited and buffered for retry.
Ingest requests are limited to 600 per minute from each network address, so every agent behind the same plant address shares that budget. If you send faster than that, Haltless returns a rate-limit response. The agent buffers the batch and retries it later rather than dropping it. To reduce pressure, increase collection_interval_seconds, raise batch_size (up to 1000) so you make fewer, larger requests, or reduce the number of high-frequency sources. See Rate limiting for the limits.
Source device won't connect
Modbus: no response.
- Confirm network reachability to the device on its port (default 502).
- Verify the
hostandportin the config. - Confirm the device speaks Modbus TCP. A device on a serial line needs a
modbus_rtusource instead, or a gateway withunit_idset. - If values look wrong, re-check
address,register_type(holdingvsinput), andscale/offset. Remembercount: 2reads a big-endian 32-bit float.
OPC-UA: connection or node errors.
- Verify the
endpoint_urluses theopc.tcp://scheme and the host/port are reachable. - If the server needs credentials, set both
usernameandpassword; setting only one causes an anonymous connection. - If a specific node fails, confirm the
node_idexists on the server using an OPC-UA browser tool.
Siemens S7: connection refused or every value fails.
- Confirm the PLC is reachable on port 102 from the agent host.
- Check
rackandslot. A wrong slot shows only as a refused connection: an S7-300 is usually slot2, an S7-1200 or S7-1500 slot1, and an S7-400's is in its hardware configuration. - On an S7-1200 or S7-1500, tick Permit access with PUT/GET communication from remote partner in the CPU's protection settings, and switch Optimized block access off on each data block you read. Both need a download to the CPU.
- If one value fails with "Object does not exist" or "Invalid address", the data block is missing or shorter than the address. Check the offset against the block in TIA Portal or STEP 7.
- If the agent will not start, the log names the machine, the tag's position, and its address. The usual cause is a size letter that does not match the type, for example
DBWwith aREAL.
EtherNet/IP: connection or tag errors.
- Confirm the controller is reachable on port 44818 from the agent host.
- For a ControlLogix, set
slotto the controller's slot in the chassis. A CompactLogix or Micro800 needs0. - A tag that does not exist is skipped and logged. Program-scoped tags need their
Program:<name>.prefix, exactly as in Studio 5000. - A tag that holds a
STRING, a whole UDT, or a whole array is skipped: point at a numeric member or element instead. - For an SLC 500 or MicroLogix, set
controller: slcand use data-file addresses such asN7:0orF8:2.
Modbus RTU: no response or garbled values.
- Confirm
portnames the serial adapter (ls /dev/ttyUSB*). In Docker, check the device is passed into the container and the agent's user has its group. - Match
baudrate,parity, andstopbitsto the devices. Many ship at 9600 baud with no parity (parity: N). - Check each register's
unit_idagainst the device's address. On RS-485, check the A/B wiring and the line termination. - A second source on the same port is refused at startup: list every device on the bus under one source.
Mitsubishi MELSEC: connection refused or every value fails.
- Confirm the CPU has an MC protocol (SLMP) connection open on
port, set to TCP and binary code. A connection set to ASCII code or UDP does not answer the agent. - On an iQ-F (FX5), set
xy_numbering: octal. - A device logged as refused, with an end code, does not exist on the CPU or is out of range. Check its number against the CPU's device settings.
Omron FINS: no answer, or addresses refused.
- Confirm the PLC is reachable on UDP port 9600 from the agent host.
- If the PLC's FINS settings use an IP address table instead of automatic address generation, set
destination_nodeandsource_nodeto match. - An address logged as refused, with an end code, is outside the CPU's memory. Check the area and word number.
Beckhoff ADS: connection or symbol errors.
- Add a route on the PLC (TwinCAT, System, Routes) for the agent's IP address and AMS Net ID. Without one the PLC does not answer.
- Check
ams_port:851for TwinCAT 3's first PLC,801for TwinCAT 2. - A symbol logged as refused is not in the PLC program under that name. Check the full path, such as
MAIN.speedorGVL.Pumps[2].count. - One cycle reports an error after a download or online change, while the agent reconnects for the new symbol table. That is expected.
- Every ADS source on one agent must use the same
local_ams_net_id. A source with a different one is refused at startup.
BACnet/IP: no answer, or objects refused.
- Confirm the device is reachable on UDP port 47808 and that the firewall lets its answers back to the agent. If the firewall needs a fixed port for the agent, set
local_port. - A device behind a BACnet router (an MS/TP field bus) is not reached yet. Read a device with its own IP address.
- An object logged as refused does not exist on the device, or does not have that property. Check the type and instance in the device's object list.
MQTT: not connected, or no readings.
- Confirm the broker is reachable on its port (1883, or 8883 over TLS) from the agent host. The log gives the reason the first connection failed (refused, a name that does not resolve, a TLS error such as TLS against a plain listener), the broker's reason when it refuses the login or a subscription or drops the connection, and one warning while it stays unreachable.
- Check that each filter in
topicsmatches what your plant publishes. Runningmosquitto_sub -h <broker> -t 'plant/line3/#' -von the agent host shows the topics and payloads the agent will see. - A topic logged as having no number to read publishes text, an array, or a payload that is not UTF-8. Point
topicsat the numeric topics, or publish a JSON object with numeric fields. - Two clients with the same
client_idknock each other off the broker. Give each agent its own, or leave the default. - A tag that stops reporting has had no new message for
hold_seconds. Raisehold_secondsfor topics that publish rarely.
Sparkplug B: no readings.
- Check the broker connection as for MQTT, then
group_id,edge_node_id, anddevice_idagainst what the edge nodes publish:mosquitto_sub -h <broker> -t 'spBv1.0/Plant1/#' -vshows their topics. - Values sent by alias are skipped until the agent has seen the node's birth. The agent sends no rebirth requests, so restart the edge node or have it publish a new birth.
- Strings, data sets, templates, and historical (store-and-forward) metrics are not read.
AVEVA PI Web API: refused, or no values.
- Confirm
base_urlopens from the agent host, and that Basic authentication is enabled on the PI Web API server. - A refused login (
401or403) means the username or password is wrong, or the account lacks read access in PI. - A path logged as not found does not exist under that name. Check it in PI System Explorer, and write it in single quotes in
config.yamlso YAML keeps its backslashes. - A point in a system state such as
ShutdownorI/O Timeoutis skipped until it reads a value again.
CSV / JSON: no readings.
- Confirm the watched
directoryexists and the agent's user can read it. - Check that column names (
col_*) or object keys (key_*) match your files exactly. - Remember CSV files are read incrementally (only new rows each cycle) and JSON/JSONL files are processed once, so drop each new export in as a new file.
Still stuck?
Collect the recent logs with the API key and any passwords redacted, note the agent version and the exact error text, and share them with your Haltless support contact. The Getting started guides and the API reference cover the wider platform if the issue turns out to be downstream of the agent.