Common issues
Detailed, symptom-first walkthroughs. For quick symptom→page routing see the troubleshooting index. For every error code’s exact definition see the error reference.
Install failures
Section titled “Install failures”Symptom: X400903INVAPP — “invalid app”
Section titled “Symptom: X400903INVAPP — “invalid app””Install aborted validating the app definition. Causes, in rough order of likelihood:
- Required variable not provided. The app declares
vars.admin_password@secret!but you didn’t pass--var admin_password=.... Check which fields are!(required) in the app’svars:block orvars.yaml. - Required plug has no viable socket. An app with
required: trueon a plug refuses to install unless a matching socket exists. Either install the provider stack first, or pass-i <provider>. - Integration spec doesn’t resolve.
Error detail says “no matching sockets found” or
“multiple plugs match” — use the explicit
-i plug=provider.socketform. See plugs-and-sockets — CLI spec. - Malformed
app.yamlorvars.yaml. YAML parse error or a field of the wrong type. Runshc validate <path-to-app-dir>to get a precise schema error.
Symptom: X409003STKEXS — “stack already exists”
Section titled “Symptom: X409003STKEXS — “stack already exists””A stack with that (tenant, stack, environment) tuple is already
installed.
shc stack list --all-tenants # find the conflicting stackshc uninstall <stack> -e <env> # remove it, OR…shc install <app> -s <name> -e <env> # pick a different stack/envSymptom: Stack starts but immediately fails
Section titled “Symptom: Stack starts but immediately fails”Install reached compose up but containers exit. The install flow
waits for a healthcheck window (configurable per-app via
timeouts.start) and then rolls back.
shc stack logs <stack>— raw container logsshc logs tail --filter stack=<stack>— daemon lifecycle eventsshc -s <stack> events— structured events- Check
X500907STKFALdetail in the logs — it includes which service’s healthcheck failed
Common root causes:
- Missing integration variable: template rendered
DATABASE_URL=postgres://user:@host/db(empty password) → provider integration hadn’t finished when compose started. Re-run with-i <provider>to wire the integration, or install the provider first. - Image architecture mismatch: running on arm64 but the
app’s image is amd64-only.
Check
requires.architecturesinapp.yamland the image’s manifest. - Port already in use: something else on the host is bound to a
public_portstarget.ss -tlnp | grep <port>. - Volume mount failed: the storage provider refused the mount
path — check
shc doctor run --category storage.
Symptom: Install hangs on “pulling image”
Section titled “Symptom: Install hangs on “pulling image””Docker pull is slow or unreachable from the target node.
# On the target node:docker pull <image> # reproduce directlyshc node ls -v # find which node it targetsshc inspect deployment <stack> # see assignmentIf pulling works manually but SHC’s pull hangs, the issue is usually Docker daemon health. Restart Docker on the node.
Symptom: X404002APPNOF — “app not found”
Section titled “Symptom: X404002APPNOF — “app not found””The app name isn’t in any registered repo.
shc repo list # check repos are configuredshc repo sync # force-refreshshc search <partial-name> # confirm the app existsIf it’s a local file path repo, make sure shc has read access to
the directory.
Integration issues
Section titled “Integration issues”Symptom: X400903INVAPP — “no matching sockets found”
Section titled “Symptom: X400903INVAPP — “no matching sockets found””Auto-match failed because the consumer’s plugs don’t match any of the provider’s socket names.
# See the consumer's plugs:shc inspect app <app> # lists plugs + their target socket names
# See the provider's sockets:shc inspect stack <provider-stack>If the names don’t match (e.g. your plug wants postgres but the
provider exports database), use explicit syntax:
shc install myapp -i postgres=provider-stack.databaseSymptom: X404007PLGNOF / X404006SOKNOF
Section titled “Symptom: X404007PLGNOF / X404006SOKNOF”Plug or socket name in your -i spec doesn’t exist on the named
stack. Typo or stale spec. Verify names with shc inspect stack.
Symptom: Consumer renders a literal {{ integrations.X.Y }}
Section titled “Symptom: Consumer renders a literal {{ integrations.X.Y }}”The integration didn’t register before compose rendered, so the template variable was unset. This almost always means the integration phase job failed silently.
shc integration list <stack> # verify the edge existsshc job runs <stack> # find the failed integration-phase jobshc job show <run-id> # get full stdout/stderrRe-run the integration:
shc integration delete <stack> <plug> # remove the broken edgeshc integration create <stack> <plug>=<provider>.<socket>Symptom: X400954XNDSCK — cross-node socket blocked
Section titled “Symptom: X400954XNDSCK — cross-node socket blocked”The provider’s socket has local_only: true, meaning it can only
serve consumers on the same node. Your consumer is on a different
node.
Options:
- Install the consumer on the same node
(
--node <same-as-provider>) - Remove the
local_onlyrestriction in the provider app’s socket YAML (only appropriate if the exposed service is actually mesh-reachable)
Backup and restore issues
Section titled “Backup and restore issues”Symptom: Backup fails with X500908HKFAIL
Section titled “Symptom: Backup fails with X500908HKFAIL”A pre_backup or post_backup hook (or a backup converter)
failed. SHC logs the job error but does not abort the backup —
so this is a warning, not a fatal error, unless the backup
evidently lacks expected content.
shc job runs <stack> # find the failed hook runshc job show <run-id> # full outputThen:
- Run the job manually against the same stack to reproduce
- Check if the job expects a service that’s not yet running (run it at a different hook phase — see lifecycle-hooks — allowed contexts per hook)
Symptom: “restic repo not reachable”
Section titled “Symptom: “restic repo not reachable””Backup failed before capture because restic couldn’t access its repo.
- Confirm
backup.repoin your config points at a reachable location - For S3/B2 backends, confirm the
backup.passwordreference resolves — test withshc vault get <ref> - For NFS/local shared repos, confirm the mount is present on every node (the node doing the backup is the node the stack lives on). See storage-providers — NFS.
Symptom: Restore succeeds but stack won’t start
Section titled “Symptom: Restore succeeds but stack won’t start”Restore completed (metadata, volumes, vault secrets, integrations) but the restored stack’s containers fail to come up.
Most common causes:
- Integrations didn’t replay. Restore re-creates integration
rows but may skip re-running the
integrationphase jobs if the provider is unavailable at restore time. Checkshc integration list <stack>— if edges exist butwait_forjobs aren’t present in provider DBs, manually re-run:Terminal window shc integration recreate <stack> - Vault scope mismatch. Restoring into a different tenant or
environment means
ref+vault://...?scope=deploymentreferences resolve to nothing until you set values in the new scope. See vault — scopes. - Image version drift. The app was installed at v1 but now
resolves to v2, and the restored data is v1-compatible. Pin
versions in
app.yamlor use a repo at a fixed ref.
Cluster issues
Section titled “Cluster issues”Symptom: shc node join hangs at “enrolling”
Section titled “Symptom: shc node join hangs at “enrolling””The joining node can’t reach the leader’s enrollment API (TCP 4003) or can’t complete the mesh handshake (UDP 4242).
# On the joining node:curl -v https://<leader-ip>:4003/health # should succeed (mTLS error is OK - means TCP works)nc -zvu <leader-ip> 4242 # UDP reachability test
# On the leader:ss -tlnp | grep 4003 # internal API listeningss -ulnp | grep 4242 # Nebula listeningSee cluster join flow for the full handshake.
Symptom: shc node ls shows a node as unhealthy forever
Section titled “Symptom: shc node ls shows a node as unhealthy forever”Either the node is actually dead (see disaster recovery — Scenario 3) or the healthcheck probe can’t reach it.
# On any healthy voter:shc cluster status # node counts, voter/quorum state, leadershc cluster ping <node> # probe that node directlyshc cluster mesh diagnose # overlay reachability, every node pair
# Run this check from the "unhealthy" node itself if you have shell:shc node ls # if it also sees itself as lonely, # mesh is broken from its sideSymptom: Writes return 503 / X500021CLSINT
Section titled “Symptom: Writes return 503 / X500021CLSINT”Raft lost quorum. Recover per disaster recovery — Scenario 2.
Symptom: Certificate rotation failing
Section titled “Symptom: Certificate rotation failing”Logs show cert expiry warnings or rotation errors.
shc cluster cert ls # report cert ages across the clustershc cluster cert rotate # force-rotate everything that's dueshc cluster cert rotate --type mtls # rotate only one categorySee disaster recovery — Scenario 5.
Vault issues
Section titled “Vault issues”Symptom: Daemon won’t start — “vault password not configured”
Section titled “Symptom: Daemon won’t start — “vault password not configured””X503902VLTNAV at startup. The daemon tried to unlock the vault
and found no password in env, file, or command.
Configure one of:
SHC_VAULT_PASSWORDenv var (best for systemd LoadCredential)vault.password_filein configvault.password_commandin config
See vault — unlocking.
Symptom: X401011VLTDEC — decryption failed
Section titled “Symptom: X401011VLTDEC — decryption failed”The stored blob can’t be decrypted with the current master password.
- You recently rotated and the rotation didn’t finish → restart daemon, retry
- You restored a backup from a different cluster → rotate to the backup’s original password, then rotate again to your new one
- Hardware corruption of the state database (
/var/lib/shc/shc.db, or/var/lib/shc/rqlitein cluster mode) → restore from a clean backup
Symptom: ref+vault://key renders unresolved
Section titled “Symptom: ref+vault://key renders unresolved”The resolver returned None for the key. Checks:
- Does the value exist?
shc vault exists <key>at the exact(scope, tenant, stack, environment)you expect. - Is the scope in the URI what you think?
?scope=tenantvs?scope=deploymentresolve against different keys. - Is the vault healthy?
shc doctor run --category vaultreports lock state and flags broken (empty-value) rows. An unreachable external provider surfaces asX503902VLTNAV(vault not available) in the daemon log. - Did you autogen?
?autogenonly generates on first render; if the first render failed (vault write error), the key may still be absent on retry. Check forX500901OPFAILin logs.
Daemon won’t start
Section titled “Daemon won’t start”Check order
Section titled “Check order”# 1. Is there actually a process?systemctl status shc
# 2. What did it log?journalctl -u shc -n 200
# 3. Can it reach its state dir?ls -la /var/lib/shc/sudo -u shc test -w /var/lib/shc && echo OK
# 4. Are required ports free?ss -tlnp | grep -E '4001|4002|4003'ss -ulnp | grep 4242
# 5. Is the raft engine's data corrupt? (cluster mode)ls -la /var/lib/shc/rqlite/# Look for a fresh 'raft.db' and old logs. If this is a cluster# member whose peers are gone, see disaster-recovery Scenario 2.Common startup failures
Section titled “Common startup failures”- “address already in use” — another process owns the port.
Find it with
ss -tlnp. SHC’s internal API is TCP 4003 by default; change viainternal_api.portin config. - “permission denied” on
/var/lib/shc/...— systemd user/group mismatch. Verify withstat /var/lib/shcandid shc. The state dir must be owned by theshcuser. - “vault password not configured” — see vault section above.
- “raft: failed to open snapshot” — the raft engine’s snapshot is corrupt. See disaster recovery — Option C: restore from backup.
Getting more signal
Section titled “Getting more signal”If none of the above helps, turn up the logging and re-run:
# Run the daemon in foreground with debug logging:systemctl stop shcsudo -u shc SHC_LOG_LEVEL=debug shc daemon…and inspect the full structured log (not just human messages)
via shc logs tail (on a separate shell) while reproducing the
issue.
For production incidents, gather the diagnostic surfaces into one place before opening an issue:
shc doctor run -o json > doctor.json # every registered diagnosisshc version -o json > version.json # version + build infoshc cluster status -o json > cluster.json # voter/quorum state, leadershc node ls -o json > nodes.json # membership + node healthshc events --audit -n 200 > audit.log # recent audited actionsjournalctl -u shc --since -2h > daemon.log # structured daemon logRelated
Section titled “Related”- Error reference — every error code SHC emits
- Disaster recovery — cluster-level failures
- Observability — logs, metrics, traces
- Flow docs — step-by-step pipeline traces