Zum Inhalt springen

Troubleshooting & diagnostics

When something is wrong on a running deployment, resist the urge to restart first and ask questions later. Taranac is a stack of cooperating services, and a blind restart both loses the evidence and often just resets the clock on the real problem. This page is a diagnostic-first runbook: gather a small, specific set of read-only facts, read them, then act. Every command in the bundle below is safe to run on production — none of them change state.

It is also a symptom index. Most of what breaks in the field has a name and a page that owns it, and a large share of it has already been cured by a release. So the index routes rather than restates: find the symptom, read the one-line cause, and follow the link — or read the Cured in column and upgrade instead of investigating.

Run these read-only commands and keep the output — this is the baseline every other section builds on.

Terminal window
./taranac version # installed version (+ whether a newer one exists)
./taranac ps # per-service status: is anything down or restarting?
top -b -n1 | head -20 # host CPU + memory snapshot, busiest processes first
docker stats --no-stream # per-container CPU / memory / IO
df -h # disk usage per mount (a full disk breaks everything)

./taranac ps is the single most useful line: it tells you whether this is a service problem (something is Exited or Restarting) or a host problem (every service is up but the box is starved). Read it before anything else. The names every command below takes are the compose service names — tacacs, radius, nac, api, postgres, edge, frontend, captive-portal and taranac-mfa among them.

Where a release cured a symptom, Cured in names it. Where it says still live, the behaviour is current at 1.2.8 — those are collected in Symptoms no release has cured below.

Device administration — TACACS+ and RADIUS

Section titled “Device administration — TACACS+ and RADIUS”
SymptomWhat it is, and what to doCured in
Login succeeds and reaches the right privilege level, but every command is denied — the authorization log shows a refusal with an empty profileThe device omits the RFC 8907 = / * separator and sends a bare <cr> as an argument of its own (Edgecore ECS4100 is the reported platform). The request was treated as invalid and discarded before any policy rule was considered, which is why it pointed at the policy. Vendor Compatibility on AAA → Settings → TACACS+ governs the tolerance and is on by default.1.2.4
Every command denied, and it started the moment someone opened a seeded profile and pressed SaveThe seeded profile’s stored command default was empty, which the renderer read as permit and the form as deny — and the form submits what it shows. An existing profile with no stored value now displays permit, which is what the daemon is doing.1.2.8
The UI and the AAA log both show the profile as applied, but the attribute never reaches the deviceFour separate causes, each with its own cure: a RADIUS attribute identified by vendor + attribute number rather than by name (1.2.4); a RADIUS template created after the daemon started, read once into a cache nothing refreshed — raw attributes still worked, which is why they looked like the only thing that did (1.2.8); a raw AV-pair sharing a service with the template, left behind a block that always terminated (1.2.8); a profile built on a non-shell vendor template reduced to a generic shell / privilege-15 block (1.1.2).see cause
The device refuses the whole authorization over an attribute it does not implementA template that declared an attribute optional had it rendered mandatory — = where RFC 8907 wants *. The shipped example is the built-in Cisco IOS-XR template, whose task attribute went out mandatory in every deployment. On the device the symptom is indistinguishable from a policy problem.1.2.7
Group matching never succeeds on a FortiGateThe seeded template named the AV-pair member_of; a FortiGate asks for memberof, so it was silently dropped. Migration 0019 renames it in existing templates and profiles too.1.2.2
Saving a profile nobody edited returns 422The directive-name whitelist banned # outright, locking out Cisco’s numbered families — inacl#1, outacl#1, route#1, interface-config#1. # is the daemon’s comment character only at the front of a token, so that is where it stays banned.1.2.8
A single-valued RADIUS attribute goes out carrying every alternative at onceA Palo Alto role could be sent as superuser, superreader and deviceadmin in one Access-Accept, with nothing to say which the device should believe. The template already declared the answer and nothing read it.1.2.8
A user sees “Enable authentication failed” after typing the correct password at an ordinary login prompt — and was never asked for a second factorTACACS+ logins sent as PAP (a single packet) were classified as enable requests, and enable deliberately does not ask for MFA. Affects any device that sends PAP for SSH; Extreme SLX-OS is one.1.2.5
The Password prompt you configured never appears on the deviceBefore 1.2.7 no code put it in the generated daemon configuration — the compiled-in Password: was sent regardless. Two things to know where it now works: the value applies only while MFA is on, and only an interactive (ASCII) login can display it at all — a PAP device carries the password in its first packet and asks nothing.1.2.7
A MikroTik administrator’s successful login writes a counted bad_passwordRouterOS probes the SSH “none” method by proving the empty password before prompting the human. It halved the brute-force threshold for exactly the devices that need MS-CHAPv2, and topped the failed-authentication widget with your own administrators.1.2.2
A narrow TACACS+ rule applies to the whole fleetA device restriction that resolved to nothing — an emptied device group, or a device sitting on an FQDN network object — widened the rule instead of failing closed. The same rule already failed closed in RADIUS.1.2.2
A device silently drops out of the RADIUS clients list and is not authenticated at allAn FQDN-type network object was attached to it; such an object carries no network. Its TACACS+ rules also lost their device restriction entirely.1.2.2
TACACS+ keeps failing against LDAP values you have already corrected in the UIThe TACACS+ service read the LDAP settings once at start and kept that copy for as long as it ran — most painfully a CA certificate. It re-reads them on the same schedule RADIUS already used.1.2.4
./taranac diagnose reports “the device is using a different shared secret” and your keys are demonstrably rightA capture that starts mid-connection, or a segment boundary inside a packet, left the stream splitter misaligned and the bogus packet it produced was diagnosed as the most common real fault. On a lab capture 18 of 19 packets decoded with the device’s key and the 19th produced that verdict. Once any packet of a session has decoded, a later failure in it is now reported as a truncated capture instead.1.2.8
SymptomWhat it is, and what to doCured in
Every 802.1X authentication from a Huawei campus switch fails, and it correlates with the client having IPv6The 802.1X service read campus switches with Huawei’s carrier attribute list. The two lists disagree about the type of eleven attribute numbers, and one of them is what a switch fills with an IPv6 address: sixteen bytes of address read as text, refused before any rule of yours was consulted. Seen on an S5735 running VRP V200R021, rejecting every EAP-TLS login. Read the caution below before you upgrade.1.2.6
The NAC authentication log goes completely silent while authentication keeps workingA Huawei switch on the newer NAS-Port-Id format sent a port description longer than the column that stored it; the write was refused, the batch was retried unchanged, and every record queued behind one that could never land. Entries lost while the log was blocked never reached the database and cannot be recovered. Device administration over RADIUS had the identical flaw waiting in it.1.2.6
Windows machine certificates are logged as PEAP, and EAP-TLS policy rules do not match themTemplates on a Windows authority commonly issue machine certificates with an empty subject. The method was decided from the subject’s common name alone, so those sessions fell back to the last-resort guess. The profile, VLAN and ACL you intended were never applied. Earlier records are not rewritten — and if you worked around this with a rule matching PEAP, it stops matching after the upgrade.1.2.6
A supplicant that does not trust the RADIUS server certificate is reported as a bad passwordA dead TLS handshake was logged as PEAP with auth_failed, sending the operator after credentials for a certificate problem. A rejected server certificate, an account that cannot use PEAP, and a missing ntlm_auth helper are each named now.1.2.2
Supplicants keep being shown the built-in certificate although the page says DeployedThe uploaded or issued 802.1X server certificate was written to a directory the RADIUS service could not read, and every layer above reported success. This had never worked in any release; a certificate already marked deployed is re-delivered at start-up after the update.1.2.4
Healthy sessions are torn down for being healthyThe orphaned-session probe asked “are you still there?” with a real Disconnect-Request, and on hardware that sends no periodic interim accounting (the Cisco default) every session eventually crossed the threshold.1.2.2
A CoA disconnect reports success, the session disappears from the UI, and the user is still on the networkA switch’s 503 answers “did I find a session matching your identifiers”, not “is anybody on the network” — it usually means the port re-authenticated in place. Nothing is archived on a NAK now, and the accounting stream stays the authority on whether a session is alive.1.2.2
An active session shows DOMAIN\anonymous, or an Accounting stop dated weeks earlier — sometimes from a different deviceRe-authentication closes one session and opens the next milliseconds apart, which lost the identity (1.2.2); and the timeline matched on the NAS’s accounting identifier alone, which equipment hands out from a short counter and reuses, so it collected every earlier session’s disconnects (1.2.4). Termination reasons are also reported by name rather than as a bare 0.see cause
A Reauthentication timer you set never reaches the hardwareThe value was carried everywhere — model, UI, reports, daemon cache — and serialized nowhere. It is rendered as Session-Timeout plus Termination-Action now, per the vendor’s own mapping.1.2.2
Over 802.1X, ANYTHING\alice and alice@whatever.invalid both authenticate as the local account aliceThe name is now tried exactly as sent, and a qualified name may only ever resolve to a directory account.1.2.2
A classification rule matches more than you wrote — PC-01 also catches PC-011, OLD-PC-01, PC-01-TESTBy design, and unchanged. A hostname, certificate-identity or vendor pattern without a wildcard is a substring match — which is the required behaviour for vendor names, where an operator types Cisco and the database holds “Cisco Systems, Inc”. The way out is a character class, which turns the value into a glob: PC-0[1] matches PC-01 and not PC-011. The field hints say so since 1.2.7.by design
CoA arrives on a port you did not configurestill live — see Symptoms no release has cured.
SymptomWhat it is, and what to doCured in
Every login starts failing with certificate verify failed after upgrading to 1.2.21.2.2 changed how an LDAP directory’s CA certificate is stored — from a path to a file inside the container to the certificate itself — and did not carry the old value across, so LDAPS was verified against the container’s built-in public CA list instead. Against a private CA that stops LDAP authentication outright, and it affects everything that talks to the directory: TACACS+ and RADIUS logins, the web sign-in, user and computer synchronisation. Updating repairs it.1.2.4
An encrypted LDAP connection with validation on and an empty CA fieldLegitimate behind a publicly-trusted directory certificate, and identical in appearance to a configuration that has lost its own. It is named on the configuration form, on Test connection, and in the service logs now, instead of surfacing as a handshake error days later.1.2.4
”AD user filters do not work in any configuration”They did. Both sync filters were rendered inside the Advanced branch, so in the ordinary view they were off screen entirely while the login-time filter was not — and the preview answered about something other than what the form was showing.1.2.8
A group brings in nobody and shows Members: 0That one number meant three different things — an empty group, a membership attribute Taranac does not read, or an entry that could not be read at all. It says which now.1.2.8
Choosing specific groups to synchronize imports nothing — no groups, no users, no error, green status, zeroes across every counterobjectClass=group is Active Directory’s vocabulary, and LDAP does not treat “matched nothing” as an error. Affected OpenLDAP, FreeIPA and every other non-AD server; searching by name also missed accounts whose login attribute is uid rather than sAMAccountName.1.2.4
A group appears in your user list as a personIn an AD domain a group carries a login-style attribute, so a nested group was created as an ordinary account — active, listed, usable in policy — while the people inside it were never imported. Accounts created this way before the update are left alone and can be removed by hand.1.2.4
A local account is a permanent member of an imported group and neither the sync nor the UI can remove itThe sync only ever removed members it had itself imported from that directory. Membership is simply whatever the directory says now, and a removal is recorded in the audit log. This removes a membership, never an account.1.2.4
Google Workspace accounts import and then every one of them is refused at sign-in, password reported wrongThe password check was succeeding; immediately after it the LDAP library asked the directory to describe itself, which Google Secure LDAP allows only a privileged account, and the refusal arrived on the same connection and was read as a failed login. 802.1X denied the session and the attempts counted toward the domain guards.1.2.4
A directory that stops answering hangs the request instead of failing itThe LDAP client read a boolean server pool as “retry forever”. On every operation including Test connection — which is exactly where an operator goes to find out what is wrong — and inside the RADIUS and TACACS+ daemons, where the loop sat in the thread serving the request.1.2.2
N binds against your directory every interval, N audit rows and N copies of every failure email on an N-node clusterThe directory sync ran on every node at once, racing upserts on the same users and groups. It runs on the leader now.1.2.8
MS-CHAPv2 against FreeIPA reports a wrong passwordNot a regression — 1.2.8 added this mechanism, and it needs one operator action. ipaNTHash sits in an explicit exclusion list even in the administrator’s own ACI, so an ACI granting the bind account read access has to be added; the tab generates the LDIF with your bind DN and search base already filled in, and Check directory reports whether the hash is readable. A missing ACI is reported as exactly that, never as a wrong password — FreeIPA does not refuse, it answers without the attribute. See LDAP / Active Directory.new in 1.2.8
A user created moments ago authenticates but lands on a default privilege level on that first sessionAuthorization is handled separately from login and did not yet know the new user. It loads the user the same way login does now, so the matched rule’s privilege level applies immediately.1.1.2
A one-time MFA code is reused inside its validity window by directing the retry at another worker or nodeReplay protection was per process. Used codes are tracked in a shared store now.1.1.2
Taranac Push MFA cannot complete over MS-CHAPv2An account with push required could not log in at all on devices that offer nothing else. See Taranac MFA.1.2.3
System MFA Mode set to disabled and people are still challengedstill live — see Symptoms no release has cured.
SymptomWhat it is, and what to doCured in
During an incident, an authentication journal that reads as “there were no authentications”On a failed request the log pages rendered their empty state — the worst kind of failure, silent and pointing the wrong way. Failures say so now, and a failed auto-refresh over live rows says the data is the last good one.1.2.2
A critical service alert and an email on every ordinary container recreate, resolving a minute laterA daemon that has just come up refuses while its caches load — 7 to 20 seconds on a loaded node. There is a grace window now; a real outage lasts longer than it and still alerts at once. See Alerts.1.2.8
A failed RADIUS MFA challenge logs an unusable fragment of what was submittedAn expired code and a value that was never a code are no longer the same log line: the log records the shape of what arrived — its length, and whether it was all digits — never a slice of its content.1.2.2
The SMTP test answers Request failed with status code 502 and names nothingEvery failure arrived as the same line of framework text; the reason existed only in the server log. The test names the cause and the setting to change now, quotes the mail server’s own reply where there was one, and calls an untrusted certificate by its name rather than blaming the port.1.2.6
Your SIEM receives every AAA record several times on a clusterstill live — see below.
Audit archiving is enabled and there are no JSONL files anywherestill live — see below.

High availability documents the mechanics of every row here; this table only indexes the symptom.

SymptomWhat it is, and what to doCured in
member count is unequal on a joining machine, or a rebuilt node whose etcd refuses to startA stale declared etcd member list. Adding a node updates that list on the node you ran the command from and deliberately not on the others, and the preflight used to call that drift that “breaks nothing right now”. It breaks two things: etcd compares the list against the live ring whenever a member starts on an empty data directory, and cluster add-node hands its list to the node being added. The preflight now stops the add and names the members that differ; ./taranac cluster sync is the cure and now actually reconciles that list.1.2.7
A machine nobody wiped silently rejoins a cluster you tore down and rebuilt under the same nameetcd derives a ring’s identity from a token plus its first members, and the token was the cluster’s name — so a rebuild produced a byte-identical identity. A cluster draws its own identity now and discards it when dismantled. Existing clusters keep the identity they have; there is nothing to migrate.1.2.7
docker compose -f docker-compose.yml up -d typed on an HA nodeIt puts an ordinary, writable PostgreSQL on Patroni’s data directory — two write histories on one data set, which no failover can reconcile. ./taranac and install.sh always refused it and both are bypassed by typing compose directly, so since 1.2.7 the refusal lives in the compose file itself. Start the node with ./taranac up -d.1.2.7
The database is in a restart loop after updating a cluster that was converted before 1.2.3Patroni’s control API became authenticated unconditionally in 1.2.3, and every path that creates a node has provisioned PATRONI_REST_PASSWORD since — the path every existing cluster takes did not. The failure surfaced three steps later as a container in a restart loop. The upgrade provisions it now, identically on every node, derived from the master key the cluster already shares. A cluster that already has a password keeps it.1.2.7
./taranac cluster add-node --address <addr> --reinstall answers unknown option '--reinstall'Three refusals printed that exact command as the cure, the runbook documented it, and --help listed it. The behaviour was implemented in full; only the two lines accepting it on the command line were missing. add-node and add-witness both take it now.1.2.7
./taranac update on a witness ends in unset-variable warnings and unable to get image '/radius:'It ran the node’s update on the arbiter. The visible failure was the lucky outcome — a witness whose .env did carry an image prefix would have started PostgreSQL, the API and the AAA daemons on the host whose whole purpose is to be cheap to lose. The updater recognises the host now: on a witness it refreshes bundle files and starts nothing, and on a standalone collector host it leaves the collector container alone.1.2.7
An upgrade that appears to succeed and leaves you on the version you started fromTwo defects that hid each other: the updater died in a reporting step with ✓ Bundle files updated as the last thing printed, before the version was pinned. An abort now names the phase it stopped in — replacing files, writing the version pin, or pulling images — and reports the bundle’s version. Separately, an update that could not fetch the bundle used to offer to continue with the container images alone, leaving compose, the wrapper, the installer and the docs behind (1.2.4 — it now stops with nothing changed, and points at ./taranac update --from taranac-bundle-<version>.tar.gz).1.2.8 / 1.2.4
The primary was upgraded although a replica was behindThe old guard printed “update the replicas first” and carried on. It refuses now, before anything is written — a warning about order is powerless the moment an earlier step lies about having succeeded, which is exactly what happened to one cluster on 15 August.1.2.8
A replica’s API restates two revision hashes and will not come upIt is waiting for a schema head only the leader can create. The message now says which way the two differ and that the order is replicas first, primary last.1.2.8
cluster status promises survivability that a switchover then contradictsThe verdict was judged from replay lag in seconds against a threshold of ours, while Patroni promotes on WAL bytes against its own. On an idle cluster both are zero, so they never disagreed on a stand. The threshold is read from Patroni’s live configuration now.1.2.8
cluster verify --rehearse refuses on a cluster built that morningIt dated the cluster from a roster row, which the surviving node keeps through a rebuild — so the gate cried wolf on precisely the cluster it exists for. It dates the cluster from the ring now.1.2.8
A node with a full disk has its backup alert cleared by a successful backup on a different nodeThe scheduled-backup alert shared one identity across the cluster, so a failover could launder a per-node fault. It is per node now.1.2.8
Disk filling up steadily over many releases, with nothing at the point of failure pointing back at the cause./taranac update fetched new images and never removed what they replaced — thirteen releases had piled up 30 GB on one demo host. Cleanup runs at the end of an update, keeps two versions (the rollback is worthless without the previous images), never touches images that are not this installation’s, and is non-fatal on every failure. ./taranac update --keep-images opts out, from the update after the one that brings you to 1.2.6.1.2.6
A backup fails outright, or an archive is quietly missing contentThree separate causes: one unreadable .env aborted the whole archive (1.2.3 — an unreadable file now degrades the archive instead); the host-files part was never collected on any deployment and the only trace was a manifest field nobody reads (1.2.2 — an incomplete archive is now loud, with a backup:incomplete alert); and config/etcd-ca was missing from every archive since the feature shipped (1.2.8) — the one protection against losing a machine and never being able to admit or replace a node again.see cause
Rotate Master Key in the admin UIIt re-encrypted only the system settings, leaving device keys, enable passwords, the internal CA’s private keys, NCM credentials, TOTP secrets and the licence on the old key while swapping the key in memory — and answered 200. The endpoint refuses now; rotation is ./taranac rotate-key, which was itself missing from released bundles until 1.2.3.1.2.2 / 1.2.3
Backups disappearing across updates, or a fresh install’s first backup returning 500The backups directory moved to a named volume so it survives container recreation (1.1.1), and the volume is initialised with correct ownership at boot (1.2.0).1.1.1 / 1.2.0
A configuration collector says “already in step with the core — nothing to do” while nothing is being collectedWhen a collector’s snapshot contract does not match its core, the core fail-closes. The core reported its version only on answers that succeeded, so the one answer that mattered — the refusal — carried none, and the single door out of the fail-closed state was held shut by the failure it exists for. The core reports it on refusals now, and ./collector-join.sh --update --version <ver> moves the collector to a version you name. See Collectors.1.2.7
After an upgrade a daemon keeps running its old generated configuration until some unrelated changeThe reload skip-gate added in 1.2.0 watches for data changes, so a release that changed how a configuration is generated did not deploy it. Each daemon reconciles its on-disk configuration once per boot now.1.2.1
The version in the dashboard, API and footer disagrees with the release you deployedA stale APP_VERSION carried over in an upgraded .env. That knob is gone — the image tag is the one version, baked in at build time. See Versioning & releases.1.1.0
SymptomWhat it is, and what to doCured in
Clicking NAC, AAA or Settings in the breadcrumb trail lands on 404 — Route not foundThose crumbs are sidebar groupings rather than pages and were being linked anyway. A crumb is a link now only when its path is a real page you may open.1.2.7
The LDAP browser draws a tree as a flat list; a group of a thousand answers “50 of 1000” foreverFive defects in the one screen you open to pick a container or a group — no indentation, a dead row highlight, an unpaged members preview, four thousand rows drawn for a level holding four thousand machines, and both panels sitting 500px tall inside a full-height drawer. Leaves are capped at 200 with the remainder counted out loud; containers are never cut.1.2.7
A list scrolls horizontally with nothing on screen explaining whyEvery list cut its cell text at a width written into that page’s own file, and the browser reserves those pixels — so the cap acted as the column’s floor. TACACS+ Profiles demanded 975px inside a 941px container. Gone from all sixteen shared tables.1.2.7
Escape in a third-level form closes all three levels and loses what was typedIt closes only the topmost drawer now, and a confirmation dialog opened from a drawer correctly takes the key.1.2.2
Creating a scrub set from inside a recipe form saves and closes the recipeThe form underneath was submitted as a side effect, and an invalid new set raised a validation message that was destroyed along with the component showing it. A form opened over another form submits only itself now — everywhere in the product.1.2.6
Editing a policy rule shows the previous profile (or the previous groups) until the page is reloadedThe save was correct; the response the form read back named the old value. Both device-administration and 802.1X policy rules were affected.1.2.4
A configuration change that saved cleanly is not reflected on the deviceCheck for a *_config:validation alert first — a failed validation is your hint that the last change did not load cleanly. See Alerts.

Every item here is current behaviour at 1.2.8, confirmed in the shipped code. They are listed because each one presents as a fault and none of them is one you can fix by upgrading.

“I disabled MFA system-wide and people are still challenged.” mfa.mode — the System MFA Mode control on Integrations → MFA — is read by no login path. Setting it to disabled greys the MFA settings page out and stops a setup link being auto-mailed to a newly created directory account, and that is the whole of its effect. The AAA policy tester does read it, which is why the tester can report MFA as not enforced on a rule the daemons will still enforce. The switches that actually stop enforcement are per path — tacacs.mfa_mode and radius.mfa_mode for device administration, and the rule’s own mfa field or mfa_required everywhere else. See Multi-factor (MFA).

Your SIEM receives up to N copies of every record on an N-node cluster. The syslog forwarder runs on every node, and the per-stream cursor it advances is shared across the cluster and taken without a lock — so each node reads and ships the same window. Nothing is lost; duplication is the entire symptom. Deduplicate on the record’s own id at the collector until the job is gated to the leader the way the directory sync now is. See Audit & syslog.

Audit archiving writes to a path no volume mounts. With audit_log.archive_enabled on, the nightly retention job writes audit_archive_<timestamp>.jsonl under audit_log.archive_path, whose default /data/audit-archive is not a volume in the shipped compose — standalone included. The files therefore live on the api container’s writable layer and go the moment the container is recreated, which every ./taranac update does. Three details make it worse than it looks: the retention purge deletes the rows whether the archive succeeded or not, the archive step swallows its own errors, and the meta-audit row records the path as archived regardless. Until the product mounts a volume for it, treat the JSONL archive as non-durable: keep the history you need in the database by raising audit_log.retention_days, and use syslog forwarding to a SIEM — or the audit log’s own export — as the durable copy.

A licence that cannot be decrypted falls back to Community in silence. The stored .lic is encrypted with the install’s MASTER_KEY. If that key no longer matches the database — a hand-rolled rotation, or a database moved between installs without its key — the file simply cannot be read, and the install reports plain Community with no problem code and no alert, exactly as if none had ever been uploaded. (A stored licence that fails its signature or its installation binding does raise license:validation; this one does not.) If Pro disappears after key or database surgery, re-upload the .lic — the file is not secret and re-uploading is always safe. See Licensing.

Two export permissions gate nothing. aaa_logs_view.export and audit_logs.export are declared in their modules and appear as switches in the role editor, but no route requires them — the AAA-log and audit-log export endpoints check view. Anyone who can read those logs can download them, ticked or not; the built-in demo role, which holds no export at all, can still pull both. nac_logs_view.export and reports.export are enforced. See Roles & permissions.

CoA can go to a port you did not configure. The CoA sender treats the value 3799 as “nothing was set” rather than as a value someone chose. So a device whose own CoA Port is literally 3799 is overridden by the next level down the chain that does carry one — a device group’s port (where that group is also the one supplying the CoA secret), or failing that the global nac.coa_port. Meanwhile the device’s Details card resolves the same chain by “is a value present”, so it goes on showing 3799 and attributes it to the device. The two resolvers disagree on that one number and no other. If any level below the device carries a non-default CoA port, do not rely on 3799 set on the device to win — clear the port on that level instead. See Network devices.

Taranac’s own alert engine watches host CPU and opens a monitoring alert (cpu:load) once usage stays above 90% for alerts.sustained_minutes (default 2 minutes) — a brief spike during a config apply or an upgrade is normal and is deliberately debounced. Sustained high CPU is what’s worth chasing.

1. Find what is hot. docker stats --no-stream attributes the load to a container. Map the busy container to a service name from ./taranac ps. Common culprits:

  • tacacs / radius / nac hot → you’re under real authentication load, or a misconfigured supplicant is retrying aggressively. Cross-check with the NAC/AAA alerts (a brute-force or unknown-device pattern shows up there) and the daemon logs below.
  • api hot → heavy admin-UI/API use, or a background job. Usually transient.
  • postgres hot → a slow or missing-index query pattern, most often driven by an unusually high AAA or logging rate. Correlate with the traffic you’re seeing.

2. Confirm it’s Taranac and not a neighbour. On a shared host, top -b -n1 | head -20 tells you whether the CPU is actually going to Taranac’s containers or to something else on the box (a backup job, another application, antivirus). Taranac can only be the cause if its processes are the ones consuming the CPU.

3. Read the hot service’s logs for the window when load climbed:

Terminal window
./taranac logs --since 30m <service> # e.g. tacacs, radius, nac, api

Look for a tight repeating line — the same client, identity, or query over and over is the usual signature of a runaway load, and it points straight at the fix (a looping supplicant, a monitoring probe hammering the API, a device misconfiguration).

Gather for a ticket: ./taranac version, ./taranac ps, docker stats --no-stream, top -b -n1 | head -20, and ./taranac logs --since 30m <hot-service>.

The alert engine opens a memory:usage alert at 85% (warning) / 95% (critical), and a separate swap:usage condition (disabled by default). Heavy swapping presents as high CPU and slowness, so rule it out early with the top / docker stats snapshots from the bundle. If one container’s memory climbs steadily over time rather than tracking load, capture two docker stats --no-stream snapshots a few minutes apart and attach both — the trend is what support needs, not a single reading.

If TACACS+ or RADIUS is timing out or rejecting, there are three states to tell apart, not two, and they have completely different fixes.

  • Service down. ./taranac ps shows the daemon Exited/Restarting, and Taranac raises a no-debounce critical reachability alert (service:tacacs / service:radius). Go to Crash-looping or exited services.

  • Service up, reachable, and refusing everything. The container is healthy, the port answers, and on a cluster cluster status may still print Healthy — but the daemon has lost the database and is answering ERROR to nearly every request. This is what the service:tacacs_backend / service:radius_backend alert exists for: critical, on that node only, after a 30-second stall. Before 1.2.8 it was worse than a wrong answer — a child that failed to get a database pool within ten seconds latched for the life of the process and kept refusing long after the cause had passed. Measured on the stand: 163 of 180 logins refused, with 17 rows in the AAA journal for 180 attempts, and the node still poisoned twenty minutes after the load stopped. On 1.2.8 the child retries, says so, and requests refused during the window are replayed into the AAA log once the database is reachable — so the gap is visible afterwards rather than lost. Check the database first, not the policy.

  • Service up, denying specific requests. The daemon is running and most things work. This is almost always policy, device, or identity configuration, not a platform fault. Reproduce one failure and read that request in the logs:

    Terminal window
    ./taranac logs --since 15m radius # or the tacacs / nac service

    A single failed request usually shows the reason inline (unknown NAS/device, wrong shared secret, no matching policy, identity-source unreachable). Recent AAA config changes are a frequent trigger — a failed validation opens a *_config:validation alert, which is your hint that the last change didn’t load cleanly. If the reason is a pattern rather than a one-off, the symptom index above is where to look next: many of the rows there present exactly as “the daemon is up and says no”.

When ./taranac ps shows a service stuck Restarting or Exited, the logs from just before it died hold the reason:

Terminal window
./taranac logs --since 15m <service>

Read the last lines before each restart — a bad config, a missing secret/certificate, or a dependency it can’t reach will name itself there. If the failure started right after an upgrade, check ./taranac version against the versioning & compatibility notes. Only once you know why it’s failing should you ./taranac restart <service> — a restart on an unfixed cause just resumes the loop.

Two loops have a known cause and a documented cure rather than a log line to read:

  • The database container restarting on an HA node, after an update, on a cluster converted before 1.2.3. It has no PATRONI_REST_PASSWORD and the image refuses to start without one — cured by updating to 1.2.7 or later, which provisions it identically on every node.
  • The database container refusing to start with a message about the base compose file. Something started docker-compose.yml without the Patroni overlay on a cluster node. That refusal is the guard working; bring the node up with ./taranac up -d instead.

On an HA cluster, add the cluster’s own view before drilling into one node — a problem that looks local is often a replication or failover symptom:

Terminal window
./taranac cluster status # nodes, roles, replication lag / health
./taranac cluster verify # can this cluster survive losing a node?
./taranac ps # local container health on THIS node
./taranac diagnose --ha # this node's whole view of the cluster, for a ticket

The cluster_sync and cluster_license alert sources surface HA-only conditions (replica lag, a node down, an orphaned replication slot, the config-apply readiness gate). Run cluster status and cluster verify on a database node — on a witness they explain why that host has neither the database nor the API they drive. See High availability for the full failover and recovery model, and the cluster rows of the index above for the symptoms this window produced.

If the first-response bundle and the relevant section above don’t point at a cause — or the fix would be destructive (a restore, a downgrade, anything touching the master key) — open a ticket with the gathered output rather than guessing. ./taranac diagnose packages a whole answer in one archive — one device’s traffic decoded, why a configuration collection fails, or what each node believes about the cluster — and carries no secret; see Diagnostic bundles. Include what changed most recently (an upgrade, a config edit, a traffic change); that single detail resolves a large share of cases on the first reply.