Other Wazuh products on your hosts
The connector is already on your hosts. A service uses that same host to run another Wazuh product: its binary is installed beside the connector, under its own system account, and it talks to its own product directly. Nobody opens a shell on the machine. Fleet decides which products may do that on each host. The product does the installing, from its own console.
Which products can do this is compiled into the connector rather than read off
the network, and each entry names one product's own plugin download site and its
own enrolment address. Wazuh Vesper's plugin comes from dl.vesper.wazuh.com
and enrols at connect.vesper.wazuh.com; no other site may serve either, for
that product or for any other. The products with an entry today are Wazuh
Vesper, Wazuh Mobius, which carries a second entry for a separate development
deployment, and Wazuh Pharos. The mechanism is the same for each of them: a
service appears in Fleet's Services panel once it has a plugin release
published.
A Wazuh Cloud environment has the panel once the connector Wazuh Cloud deployed for it reports it can host services, and one sentence in its place until then. An install requested for it before that, from Fleet or from another product's console, is refused with the same sentence. Wazuh Vesper is not available on Wazuh Cloud environments, by design, and the root shell control below is never offered there.
What a plugin is
A separate program, in its own process, under its own account
(fleet-plugin-<service>), started by systemd from one template unit,
fleet-plugin@<service>.service, with its own journal. The connector gives it
two files and nothing over the network: targets.yaml (also written as
targets.json), carrying the host's name and platform and whether a root shell
was granted, and bind/token.json, once. The only thing about Wazuh targets
may carry is the login the installer found on the host, when it found one. See
The Wazuh login the installer finds.
Otherwise a plugin gets the login it needs from its own service, with its
enrolment token, and the connector never sees that one. A login set from the
service replaces the one the installer found.
It gets no Fleet credential, no gateway address and no query proxy, and its traffic never passes through Fleet. So Fleet answers one question about a plugin: is this process alive and enrolled on this host. Whether the product is working is that product's own console, and the two disagreeing is a real state rather than a bug.
The unit carries the isolation: ProtectSystem=strict, NoNewPrivileges, IPv4
and IPv6 sockets only, an empty CapabilityBoundingSet, and memory, task and CPU
caps per instance. More than ten starts inside 300 seconds parks a crash-looping
plugin rather than letting it spin.
How one gets onto a host
- Somebody allows the service on the host, in Fleet, on the host's Services panel. Nothing is installed by that click.
- Somebody clicks Connect in the other service's console and picks a Fleet
host. That service calls Fleet, which checks the acting person is a member
of the workspace and that the service is allowed on that host, resolves the
targeted hosts, takes custody of the credential and records the binding.
Nothing has happened on any host yet. A service that is not allowed is
refused with one sentence, which its console can show before the click:
Vesper is not allowed on this host. Allow it in Fleet, on the host's Services panel, then connect again. - Each host finds out on its next heartbeat, 30 seconds apart. The connector downloads the plugin from that service's own download site, verifies it and writes a request file. The download host is compiled into the connector and re-checked on every redirect hop, never read from the message.
fleet-connector-reconcile, root and with no network, installs it, smoke tests the candidate as the plugin's own account rather than as root (throughsetpriv, orrunuserwhere that is what the host has), flips thecurrentsymlink and starts the unit.- The plugin reads
targets.yamland its token, enrols with its own service, deletes the token once that enrolment succeeds, and writes a status file every 30 seconds.
The connector daemon cannot do step 4: its unit cannot reach systemd, create an
account or write /usr/local. Installing plugins never widens the sandbox of the
internet-facing process. A host where the installer found
no useradd has no plugin accounts, and says so.
Fleet's own Services panel installs nothing. Its Allow button is the permission the other product's Connect needs, and its Disallow button takes that permission back.
The credential that service needs
The enrolment token belongs to that service, and Fleet is carrying it to a host
that may be rebooting. It is encrypted before storage, bound to the
workspace it was sent for, so a copy lifted out of one workspace's row does not
decrypt for another. No path keeps it in the clear: with no key available the
call is refused with 503 rather than stored. Fleet holds it for at most an
hour. A service sends either one token every targeted host shares or
one per host, and has to say which: a single-use key fanned out to a cluster
enrols one node and is refused by the rest, silently.
On the host it is 0600, owned by the plugin's account, and delivered once:
the connector keeps a root-owned marker of the hand-over, because the plugin
deletes the token after a successful enrolment and its absence cannot mean "not
delivered yet".
The Services panel
On the host's page, the panel puts two things side by side and never merges them: what Fleet allows, per service, and what the host says. A service allowed an hour ago that its product has not connected yet is the case somebody has to see, and one status word for it would have to lie one way or the other.
The permission is one of four words, with who changed it and when:
| Fleet says | What it means |
|---|---|
| Not allowed | The default. The product cannot install here until somebody allows it. |
| Allowed | Somebody allowed it. Nothing is installed until the product connects from its own console. |
| Installed | The product connected and its plugin is on the hosts. |
| Disabled | Somebody disallowed an installed service. Its plugin is stopped on every host and everything is kept. Allowing it again starts it with no reinstall. |
Then one row per host:
| Host says | What it means |
|---|---|
| Installed | The version, and when that host last reported. |
| behind vX.Y.Z | An older build. While the release is still rolling out, the host keeps it until its ring's wave reaches it. At 100 % the install is being retried on that host, with the host's own report beside it. Under a halted release the host keeps its version until the halt is lifted. |
| Waiting for its binary | Prepared here, nothing installed yet. |
| Not installed | No binary here. Pending when the product has asked for it. |
| Installed, disabled | Stopped on Fleet's Disallow. Its identity and credentials stay, and allowing the service again starts it. |
| Removed | Stopped and disabled, and its credentials are gone. The product installs the current release again when it connects. |
| Refused | The host tried the last act and could not, with the reason beside it. |
| Contract mismatch | The candidate does not speak the plugin contract this connector reads, so it was not enabled. |
A host that does not report which change it has applied is counted apart and named, never called behind: "it cannot say" is not "it is out of date". The panel also names the hosts a plugin must reach on 443, for whoever runs the firewall.
Health is not here. It is in the Host details card above, from a different call, so the two panels can never disagree about whether a plugin is running.
Allow, Disallow, and what each one does
Fleet allows, and the product installs. Allow is a permission for one service on one host. It installs nothing, and it needs no plugin release to exist. Until it is given, a Connect from that product's console is refused with the sentence above, and the product's own console can show the same sentence greyed before the click. A per-host grant is permission to open a host, never permission to allow software on it, and a call from another product's console is not a way around that. A Connect needs the acting person to be a member of the Fleet workspace, so Fleet records who asked.
Disallow takes the permission back. On a service whose plugin is installed, it stops and disables that plugin on every host on their next heartbeat, and it deletes nothing: the binary, the identity and the credentials stay, and the binding the product holds stays with them. Allowing the service again starts the plugin with no download and no new enrolment. Disallow is never refused, wherever the host runs.
Two acts sit beside the permission and neither changes it. Disconnect, on
a service its product bound, drops the credential Fleet holds, asks every host
to delete the plugin's identity and credentials and tells that product, saying
plainly when that message could not be delivered. The product's own console
can do the same from its side, on membership alone: installing escalates,
disconnecting de-escalates, and refusing would leave a plugin installed and
running with no work for ever. Remove, on a plugin nothing bound, does the
same on the hosts and tells nobody. Removing the host
uninstalls nothing on the machine: only the connector's own uninstall.sh does
that.
What the machine owner still decides
A plugin's Wazuh login is asked for in that product's own console and reaches
the plugin with its enrolment token, or it is the one the installer found on the
host, which a login set from the product replaces. The daemon discovers no login
of its own. The one file the
machine owner keeps, /etc/fleet-connector/plugins/<service>.yaml, decides a
single thing: whether that plugin's unit runs with its sandbox off.
An indexer or wazuh_api section left in that file by an earlier connector
is not read. It is not ignored in silence either: the plugin is told, in its
status, credentials are no longer read from this file: <service> receives them from its own service, or starts with the login the installer found on this host; remove the indexer/wazuh_api sections, and that sentence
reaches the console. A file that cannot be parsed grants nothing and says why.
upgrade.plugins_enabled: false in
/etc/fleet-connector/connector.yaml is the other
local veto: the host keeps updating its core, and logs and ignores every plugin
instruction.
Running a plugin's unit as root with its sandbox off is a grant, not a setting, and two independent things can make it. Either is enough on its own.
The machine owner, with one key, exec.enabled: true, in that same
per-service file on the host. Deleting the key takes that grant back.
A workspace admin, with the Root shell control in the Services panel.
It is granted per host, it appears only beside a service whose plugin
advertises the capability, which today is Wazuh Vesper alone, and only once that
plugin is installed: a service that is not installed says
Allow <service> and connect it from <Service> first. A root shell is granted to a plugin that runs.
and a service whose plugin does not advertise a shell has no control at all
rather than one that would fail. Only an admin sees it, which is the same rule as
installing a plugin and for a heavier reason. Granting is recorded and the
panel says so, naming who granted it and when; that record is kept apart from
who installed the plugin, so the two can never be read as each other.
The two sources are OR'd. A machine owner keeps their own say, and silence
from Fleet never revokes their file. A grant Fleet made is taken back only by
an explicit revocation or by removing the plugin, never by a quieter answer.
On the host, the connector's journal says which of the two allowed it, and the
plugin is told the same in its own targets.json.
The shape of the act is the same whichever source grants it. The reconcile
request carries nothing but the service name, so there is no payload for anything
on the wire to be. The content of the drop-in at
/etc/systemd/system/fleet-plugin@<service>.service.d/50-fleet-exec.conf is a
constant in the connector binary, so neither source decides what it says. The
recorded grant is 0600 and root-owned, in a directory only root can write, so a
plugin running under its own account cannot write itself one, and the authority
behind such a grant is the authenticated session that carried it rather than
anything on the host. Every read fails closed: a file that is missing,
unreadable or unparseable grants nothing. And it de-sandboxes that one unit: the
resource limits stay, and the core and every other plugin on the host keep their
own sandboxes.
Reading a plugin's health
The plugin writes a status file every 30 seconds. The connector forwards it verbatim and adds three things: whether it is alive, a refusal when the document is too large, and the reason when there is none.
Once a plugin is installed and its unit has started, its reading is one of these. Before that the row carries the same install-state words the Services panel uses (Not installed, Waiting for its binary, Installed, disabled, Removed, Refused, Contract mismatch), because until the reconciler has enabled it there is no health to report.
| Reading | What it means |
|---|---|
| Running | A fresh status. A second chip carries the plugin's own word for itself: starting, unbound, enrolling, bound, degraded, stopping or error. |
| Running, degraded | Alive, and listing what it cannot serve, one sentence each. |
| Not reporting | No status for more than three of its own intervals. |
| Crash-looping | systemd has had to restart it and it is not reporting. |
| Unit failed | systemd calls the unit failed. Nothing is restarting it. |
| Contract mismatch | Alive, and the connector says the plugin writes a plugin contract this connector does not read. The plugin cannot see what the connector writes for it until one of the two is updated. |
| Could not tell | Fleet has no readable status. Health is unknown, not bad. |
Could not tell is the console declining to guess, and is never painted as a
failure. A plugin whose last status says stopping lands there too: that
document was written by a process on its way out, so it says nothing about what
is running now, and a legitimate restart must not flash red.
What turns grey into red is somebody else's observation: the reconciler records what systemd said about the unit and how often it has had to restart it, shown with the age of that observation, and an absent count is never rendered as zero restarts. A plugin that is not alive also carries a journal excerpt: its last 20 lines, and never more than 4 KiB of them.
On the host, journalctl -u fleet-plugin@<service> covers one plugin and
sudo fleet-connector status prints what current points at for each. See
Connector logs for the reconciler and confirm units, which
log every install and rollback of a plugin. Hosts
differ mid-rollout on purpose: see connector lifecycle.