How it works
The managed block
Section titled “The managed block”Everything hostsctl renders goes between two marker lines:
# >>> hostsctl begin >>># generated by hostsctl 2026-08-11 17:15# source: /Users/you/.config/hostsctl/config.yaml — edit that, not this block
# --- local — Local development ---127.0.0.1 k8s.orb.local # orbstack# <<< hostsctl end <<<Lines outside the markers are copied into the new file unchanged. That is the central
invariant, and it is what the integration tests check against a copy of a real
/etc/hosts: system lines, other tools’ blocks and hand edits all survive an apply.
If a begin marker is found with no end marker, the block is treated as running to the end
of the file. Otherwise a truncated block would make every apply append a new one.
The write
Section titled “The write”- Render the block from the config and the blocklist cache.
- Compose the new file: everything outside the markers, plus the new block in place of the old one (or appended, if there was none).
- Refuse if the result does not contain
127.0.0.1 localhost. - Snapshot the current file into the backup directory.
- Write a temporary file next to the target,
fsyncit, copy the target’s mode and owner onto it,renameit over the target. - Prune old snapshots, flush the DNS cache.
Step 5 is why an interrupted apply cannot leave a half-written /etc/hosts: rename on
the same filesystem is atomic, so the file is either the old one or the new one.
The timestamp is not drift
Section titled “The timestamp is not drift”The block carries a generation timestamp, but hostsctl keeps the old one when nothing else
about the block changed. Without that, diff would report a change every minute, status
would always demand an apply, and apply would rewrite /etc/hosts and create a
backup for nothing. Keeping it also makes the timestamp mean what people assume it means:
when the block last actually changed.
Rendering rules
Section titled “Rendering rules”- One line per address. A name on three addresses is three lines, because that is the only
way
/etc/hostsexpresses it. - An exact repeat of an address-plus-name pair is dropped once, with a warning. Nothing else is dropped.
- The same name in two groups is kept and warned about — it is legal, and usually a mistake.
- A name that already appears outside the managed block gets a warning naming the file
and line. hostsctl does not touch that line, and in
/etc/hoststhe first match wins, so an entry above the block quietly beats one inside it.
The legacy block
Section titled “The legacy block”A block left by the older hosts-sync shell script is recognised but never removed on
hostsctl’s own initiative. apply warns that it is there and that its entries may win;
removing it takes an explicit --drop-legacy, or hostsctl migrate, which first imports
the old files into the config. See Migration.
What /etc/hosts cannot do
Section titled “What /etc/hosts cannot do”hostsctl check reports what the system would ignore rather than letting it fail silently
— wildcards, a port or a path in a hostname, an address that does not parse, a name that is
already defined outside the managed block. An error stops apply and exits 3; a warning
is printed and nothing is blocked.
The full list, level by level, is in the validation rules.