Troubleshooting
Symptom-first fault isolation: observation, probable cause, corrective action, and the escalation boundary.
How to use this section
Fault isolation modules are organized by symptom, not by error message. You observe a symptom; you do not always have an error message, and when you do it is often a consequence rather than a cause.
Each module has the same shape:
- Symptom. What you observe.
- Probable causes. In order of how often each is the cause, not in order of severity. Work down the list.
- Isolation. The check that distinguishes one cause from the others.
- Corrective action. What to do once the cause is known.
- Escalation boundary. The point at which further local diagnosis is not productive, and what to collect before you contact support.
Work the causes in the order listed. The ordering is derived from support case volume, so the first cause listed is the most common answer.
Symptom index
| Symptom | Module |
|---|---|
| Control-plane service does not start, or exits after starting | FI 7.1 |
| TLS handshake fails when connecting to the control plane | FI 7.2 |
| Connection to port 8443 is refused or times out | FI 7.3 |
Service is running but reports database: unreachable |
FI 7.4 |
FI 7.1: Control plane does not start
Symptom. systemctl is-active orchestrator-control-plane returns failed
or activating, and does not reach active within 60 seconds.
Probable causes
- The configuration file is invalid YAML, or a required key is missing.
- The TLS private key is not readable by the
orchestratoraccount. - Port 8443 is held by another process.
- The database schema version does not match the installed binary.
Isolation
Read the last 50 lines of the service journal:
sudo journalctl -u orchestrator-control-plane -n 50 --no-pager
Match the message against this table:
| Journal message contains | Cause |
|---|---|
yaml: line N or missing required key |
Cause 1 |
permission denied and a path under /etc/orchestrator/tls |
Cause 2 |
bind: address already in use |
Cause 3 |
schema version mismatch |
Cause 4 |
Corrective action
Cause 1. Validate the configuration without starting the service:
sudo -u orchestrator orchestrator-ctl config check \
--file /etc/orchestrator/control-plane.yaml
The command names the line and the key. Correct the file, then start the service.
Cause 2. Correct the owner. Do not widen the mode:
sudo chown orchestrator:orchestrator /etc/orchestrator/tls/server.key
sudo chmod 0600 /etc/orchestrator/tls/server.key
Cause 3. Identify the process holding the port:
sudo ss -lntp 'sport = :8443'
If the process is a previous Orchestrator instance, stop that instance. If the process belongs to another product, change that product's port. Do not change the Orchestrator port. Worker enrollment records the port, so every enrolled worker would need to be enrolled again.
Cause 4. The binary and the schema are from different releases. Confirm both:
orchestrator-ctl --version
sudo -u orchestrator orchestrator-ctl db version --dsn "<dsn>"
If the schema is older than the binary, run orchestrator-ctl db migrate. If
the schema is newer than the binary, do not migrate. A downgrade is not
supported, and the correct action is to install the matching binary version.
Escalation boundary
Escalate if the journal shows no message matching the isolation table, or if the
configuration passes config check and the service still fails.
Collect before you contact support:
sudo orchestrator-ctl support-bundle --output /tmp/bundle.tar.gz
The bundle contains the journal, the configuration with secrets removed, the schema version, and the package manifest. Attach it to the case.
FI 7.2: TLS handshake fails
Symptom. curl to port 8443 returns a certificate error. The service is
active.
Probable causes
- The hostname used does not appear in the certificate subject alternative name field.
- The issuing certificate authority is not trusted by the calling host.
- The certificate has expired.
- The certificate chain is incomplete: the intermediate is not installed.
Isolation
Read the certificate as presented by the server:
openssl s_client -connect <hostname>:8443 -servername <hostname> </dev/null \
2>/dev/null | openssl x509 -noout -subject -issuer -dates -ext subjectAltName
Compare the output:
- The hostname you used is absent from
subjectAltName→ cause 1. notAfteris in the past → cause 3.- Subject and issuer are identical → the certificate is self-signed, cause 2.
- Otherwise, count the certificates offered:
openssl s_client -connect <hostname>:8443 -showcerts </dev/null 2>/dev/null \
| grep -c 'BEGIN CERTIFICATE'
A count of 1 with a non-self-signed certificate indicates cause 4.
Corrective action
Cause 1. Reissue the certificate with the correct subject alternative name.
Editing the hostname value in the configuration to match the certificate is
not a fix. Workers connect by the DNS name, and that name must be in the
certificate.
Cause 2. Install the issuing authority's root into the trust store of every calling host. In a private PKI, this includes every worker node.
Cause 3. Replace the certificate, then restart the service. The control plane reads the certificate at startup and does not reload it on change.
Cause 4. Concatenate the intermediate to the server certificate, server certificate first:
cat server.crt intermediate.crt \
| sudo -u orchestrator tee /etc/orchestrator/tls/server.crt > /dev/null
sudo systemctl restart orchestrator-control-plane
Escalation boundary
Escalate if the certificate validates with openssl verify against the
intended chain but the handshake still fails. Attach the full openssl s_client output and the certificate chain, excluding the private key.
Never attach a private key to a support case. If a private key has been sent to any third party, treat the key as compromised. Reissue the certificate and re-enroll every worker.
FI 7.3: Connection refused or times out
Symptom. No TLS error. The connection does not establish.
Refused and timed out are different faults and they separate the causes:
| Observation | Meaning |
|---|---|
| Connection refused, immediately | The host was reached. Nothing is listening on 8443. |
| Connection times out | The packet did not reach a listening socket. A firewall on the host or on the path is dropping it, or the name leads to the wrong host. |
Probable causes
- Refused: the service is not running. Go to FI 7.1.
- Refused: the service listens on
127.0.0.1rather than0.0.0.0. - Timeout: a host firewall is dropping inbound 8443.
- Timeout: a network firewall or security group between client and host.
- Timeout: the DNS record resolves to a different host.
Isolation
Confirm the listen address on the control-plane host:
sudo ss -lnt 'sport = :8443'
127.0.0.1:8443 indicates cause 2. 0.0.0.0:8443 is correct.
Confirm the name resolves to this host:
dig +short <hostname>
ip -4 addr show | grep inet
An address that does not appear on the host indicates cause 5.
Test from the control-plane host itself:
curl -sS https://localhost:8443/healthz --insecure
Success locally and failure remotely narrows the fault to causes 3 or 4.
Corrective action
Cause 2. Set listen: 0.0.0.0:8443 in the configuration, then restart the
service.
Cause 3. Open the port on the host firewall to the worker subnets only. On RHEL:
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="<worker-subnet>" port port="8443" protocol="tcp" accept'
sudo firewall-cmd --reload
On Ubuntu and Debian with ufw:
sudo ufw allow from <worker-subnet> to any port 8443 proto tcp
Repeat the rule for each worker subnet. Do not open 8443 to all sources; only worker nodes and administrators need to reach it.
Cause 4. Network firewalls are outside the scope of this manual. Give your network team 3 values: the worker source subnets, the control-plane address, and TCP port 8443.
Cause 5. Correct the DNS record. Do not work around this with a hosts
file entry. Worker enrollment resolves the name independently on every node, so
an entry on the administrator's workstation only hides the fault until
enrollment fails.
Escalation boundary
Escalate only after the local curl --insecure check succeeds and the fault is
confirmed to be on the path rather than on the host. A connectivity fault
between 2 network segments is not diagnosable from the support bundle.
FI 7.4: Database unreachable
Symptom. The service is active and /healthz responds, but
orchestrator-ctl status --verbose reports database: unreachable.
The control plane starts without its database on purpose, so that a database outage does not also remove the health endpoint you need to diagnose it.
Probable causes
- The credentials are wrong: the user in
database.dsnis incorrect, or the password in/etc/orchestrator/db.secrethas been rotated on the database. - The database host refuses the connection:
pg_hba.confdoes not permit this source address. - The database is reachable but the connection limit is exhausted.
- TLS is required by the database and not configured in the DSN.
Isolation
Attempt the connection exactly as the service makes it, with the configured DSN and the stored password:
sudo -u orchestrator orchestrator-ctl db check
The command prints the PostgreSQL error unchanged.
| Output contains | Cause |
|---|---|
password authentication failed |
Cause 1 |
no pg_hba.conf entry for host |
Cause 2 |
too many clients already |
Cause 3 |
server does not support SSL or SSL required |
Cause 4 |
Corrective action
Cause 1. If the user is wrong, correct database.dsn in the configuration.
If the password has rotated, store the new one:
sudo -u orchestrator orchestrator-ctl db password
The command prompts for the password and rewrites /etc/orchestrator/db.secret
with mode 0600. Then restart the service. The control plane reads the DSN
and the password at startup only.
Cause 2. Add the control-plane address to pg_hba.conf on the database
host and reload PostgreSQL. Use the address the database observes, which
differs from the control plane's own address when NAT is present.
Cause 3. Raise max_connections, or lower database.pool_size in the
control-plane configuration. Confirm which side is exhausting the limit before
you change either; other applications commonly share the instance.
Cause 4. Append the required mode, and the database server's certificate authority, to the DSN:
postgresql://<user>@<host>:5432/<database>?sslmode=verify-full&sslrootcert=<ca-path>
Use verify-full rather than require. require encrypts the connection
but does not verify the server identity, so anything on the path can intercept
it. verify-full needs the certificate
authority that issued the database server's certificate, which is what
sslrootcert points to.
Escalation boundary
Escalate if db check connects successfully but the service still reports
unreachable. That combination indicates a fault in the service
rather than in the database or the network, and it needs the support bundle.