The unpleasant version of a WildFly automation failure is not a clean refusal. It is a management CLI process that prints nothing useful and waits until an outer CI or Ansible timeout kills it. That symptom covers several different failures: name resolution, routing, a filtered port, TLS negotiation, authentication, a controller that is not ready, or a management operation that really is slow.

First rule: keep connection establishment and command execution as separate tests. If they share one opaque timeout, the evidence is already damaged.

Define what “hangs” means

Run the smallest possible operation with shell tracing and an external deadline. Do not start with a deployment, reload, or batch. Ask the controller for its state.

timeout 20s "$JBOSS_HOME/bin/jboss-cli.sh" \
  --connect \
  --controller="remote+http://app-mgmt.example.net:9990" \
  --command=':read-attribute(name=server-state)'

Capture the exit code, elapsed time, standard error, target controller, effective user, and Java runtime. In automation, use no_log only around credential values—not around the entire diagnostic task. Redacting all output turns a secret-handling decision into an outage-handling problem.

Work upward through the connection layers

1. Resolve the name from the execution context

The laptop, Ansible controller, runner container, and target host may use different resolvers. Test from the place that actually launches jboss-cli.sh.

getent ahosts app-mgmt.example.net
resolvectl query app-mgmt.example.net  # where systemd-resolved is present

Multiple addresses matter. A client can wait on an unreachable IPv6 result before falling back to IPv4, or reach a load balancer address that was never intended for the management interface. Record the resolved address instead of assuming DNS is binary.

2. Prove TCP separately

timeout 5s bash -c '</dev/tcp/app-mgmt.example.net/9990'
# or, when available
nc -vz -w 5 app-mgmt.example.net 9990

A refusal is useful: the path reached a host and nothing accepted the connection. A timeout points toward routing, filtering, an incorrect address, or a listener bound elsewhere. On the server, compare that evidence with ss -lntp. Test the address clients use, not only localhost.

3. Inspect HTTP or TLS without the CLI

For HTTP management, a response code still proves that DNS and TCP completed. For HTTPS management, inspect the presented chain and hostname before importing anything into a truststore.

curl -vk --connect-timeout 5 https://app-mgmt.example.net:9993/
openssl s_client -connect app-mgmt.example.net:9993 \
  -servername app-mgmt.example.net -showcerts </dev/null

The important distinction is whether TLS never begins, negotiation fails, or the application rejects the request afterward. Blindly disabling certificate verification may move the failure while removing the evidence needed to fix it.

Verify the controller and authentication assumptions

WildFly can use local authentication for a CLI running on the same machine, while remote connections require the configured management authentication path. A script that works as the WildFly service user on the server can therefore fail from a runner even with the same command text.

Make the controller protocol explicit. remote+http, remote+https, the legacy native interface, and an endpoint behind a proxy are not interchangeable. Also verify that the port belongs to the management interface, not the application listener.

"$JBOSS_HOME/bin/jboss-cli.sh" --version
java -version
ps -ef | grep '[j]boss-modules'
ss -lntp | grep -E ':9990|:9993'

If the service has just started, a listening socket does not guarantee that the server is ready to process management operations. Read the server log around startup and poll a cheap attribute with a bounded retry rather than sleeping for a fixed minute.

Use both timeouts, for different jobs

The CLI supports a command timeout for management operations. WildFly documents the --command-timeout option and the interactive command-timeout command. That timeout is useful for a known long-running batch. It should not be the only guard around process startup, DNS, or a socket connection.

timeout 45s "$JBOSS_HOME/bin/jboss-cli.sh" \
  --connect \
  --controller="remote+http://app-mgmt.example.net:9990" \
  --command-timeout=30 \
  --file=controlled-change.cli

The outer timeout protects the automation step. The CLI command timeout limits how long the management operation may run. Set both deliberately and preserve their distinct exit evidence.

Make the Ansible failure useful

- name: Read WildFly server state
  ansible.builtin.command:
    argv:
      - /usr/bin/timeout
      - 25s
      - "{{ wildfly_home }}/bin/jboss-cli.sh"
      - --connect
      - "--controller={{ wildfly_controller }}"
      - --command-timeout=15
      - "--command=:read-attribute(name=server-state)"
  environment:
    JAVA_HOME: "{{ effective_java_home }}"
  register: wildfly_state
  changed_when: false

- name: Require a running controller
  ansible.builtin.assert:
    that:
      - "'running' in wildfly_state.stdout"
    fail_msg: "WildFly management endpoint responded, but server-state was not running."

Use argv so shell quoting is not part of the mystery. Mark a read-only probe unchanged. Keep connection checks in a preflight block, and only run the modifying CLI file after preflight succeeds. A rescue block can print sanitized DNS, port, runtime, and service-state evidence.

The useful outcome is a smaller failure

“The CLI hangs” is not actionable. “The runner resolves the management name to an unreachable IPv6 address,” “TLS presents a certificate without the requested hostname,” or “the controller accepts TCP but is still starting” is. Once the failure is that small, the fix usually stops being dramatic.

For upstream behavior and timeout controls, see the WildFly Admin Guide. For the automation around this diagnostic path, see my Ansible and Linux automation work.