Put a machine on the overlay, then get on with the work
Conflux is one binary that carries the anchor daemon and its control tool. On VeilNet's public network it enrols the machine itself; on a guardian of your own it installs the file the guardian issued. Either way it starts an anchor and registers a boot service. Joining takes one command, and coming back after a reboot takes none.
On this page
Install
Download the build for the platform from the pre-release page, check it against SHA256SUMS, and install it somewhere that survives a reboot.
$ sha256sum -c SHA256SUMS --ignore-missing conflux-linux-amd64: OK $ sudo install -m 0755 conflux-linux-amd64 /usr/local/bin/conflux $ conflux version
Per-platform notes
| Platform | What else is needed |
|---|---|
| Linux | Nothing. TUN is in the kernel, and the service gets it through CAP_NET_ADMIN. Where SELinux refuses to run the extracted binaries, conflux detects it and prints the commands that fix it. |
| macOS | Apple silicon only; there is no Intel build. utun is in the kernel, so there is no driver to install. Releases are not notarised: if macOS refuses a downloaded binary, clear the quarantine attribute with xattr -d com.apple.quarantine conflux-darwin-arm64. |
| Windows | Run PowerShell as Administrator. TUN mode also needs wintun.dll, which conflux downloads whenever a TUN machine needs it and checks against a pinned digest. If the download fails, it prints where to put the file by hand. conflux proxy needs no driver. |
| FreeBSD, OpenBSD | Everything works as on Linux. The boot service is an rc script: enabled with sysrc and run under daemon(8) on FreeBSD, enabled with rcctl on OpenBSD. |
Upgrading to a newer build
New builds of a pre-release are published under the same tag, so the version number alone does not tell two of them apart. conflux version also prints the commit a binary was built from, and that is the part to compare. To move a machine onto a newer build, replace the binary and run conflux start.
$ conflux version conflux 1.0.0-pre (f47de0fac83d24489664ca15fc6a317ac9c9f683) linux/amd64 $ sudo install -m 0755 conflux-linux-amd64 /usr/local/bin/conflux $ sudo conflux start
The new build's anchor extracts into a directory of its own rather than over the one the running daemon has open, and start restarts the service onto it. Nothing is re-enrolled and the identity is untouched. Until the restart, conflux status reports the binaries as stale.
Removing
$ sudo conflux uninstall $ sudo rm /usr/local/bin/conflux
uninstall removes the boot service, the configuration and the identity. There is no other copy of the identity anywhere, so it asks first.
Quick start
Run conflux up on one machine, then run it on every other machine with the taint it printed. There is no account to create and no key to manage.
$ sudo conflux up
An IPv4 lets the other machines on this network reach this one by a v4 address.
A private address is advertised to them; give each machine that should be
reachable on its own a different one. The IPv6 address is derived from this
machine's identity and needs no answer.
IPv4 [e.g. 10.128.0.7/24, blank for none]: 10.128.0.1/24
Minted a taint for this network:
brhk-2mq9-tzva-6pjs
Share it. Any machine that runs
conflux up --taint brhk-2mq9-tzva-6pjs
joins this network and nothing else.
Starting.
anchor anchor6btpa3gn6w4stipba4hekzho7caw6srfyy5puvbz7mfanaiept5a
overlay fd80:c4b9:99ae:4411:c531:fd4f:754f:f08a/48
ipv4 10.128.0.1/24
interface anchor0
taint brhk-2mq9-tzva-6pjs
credential valid until 2026-10-06T06:35:43Z (29d 23h)
service active (systemd: conflux.service, enabled at boot)$ sudo conflux up --taint brhk-2mq9-tzva-6pjs --ipv4 10.128.0.2/24
Passing --taint and --ipv4 answers the prompt in advance, so this is safe to run from a provisioning script. The IPv4 question is asked once in a machine's life. The answer is kept, blank included, and running up again without the flag leaves the address alone. With nobody at a terminal to answer and neither --ipv4 nor --no-ipv4 given, conflux exits rather than wait.
$ ping 10.128.0.1 $ sudo conflux status $ sudo conflux peers
Give it a minute: peers find each other by gossip, which takes anywhere from fifteen to sixty seconds. A reboot needs nothing typed. The service is registered and enabled, the credential renews itself, and the machine comes back at the same overlay address.
Next, expose a service: a database, a dashboard or a self-hosted app, reachable from every machine that shares the taint and from nowhere else.
Concepts
Conflux runs an anchor, and the anchor protocol's model is covered in full in its own documentation. Three parts of it explain most of what conflux does.
An anchor is an identity
An anchor's name is its public key. The AnchorID conflux prints is derived from a 32-byte seed, and so is the overlay IPv6 address beside it. Nothing assigns either. The address stays the same for as long as the seed survives, which is why conflux enrols exactly once. It also cannot be changed, which is why losing the seed loses the machine's place on the network.
A realm is admission control
A realm is a root key and a credential per member. An anchor presents a chain proving it descends from the root, and the chain is checked during the handshake, before any data flows. Membership is a signature, not a list on a server. The public network's machines join ghost realm alpha; an organisation's machines join a tree of its own, with its guardian at the root. The architecture shows both.
Overlay addresses are derived, not assigned
Every anchor has an IPv6 address computed from its identity and its realm. It needs no coordination and cannot collide.
IPv4 is different, which is why up asks for one. Thirty-two bits is too few to derive without collisions, so a machine's IPv4 is chosen by its operator. It never crosses the overlay as IPv4: anchor translates traffic sent from it into IPv6 from the derived address. So nothing has to arbitrate it, and two machines may share one, though a machine that should be reachable at its IPv4 needs an address of its own.
A private address is advertised, and peers reach the machine at it. A public one is sent from and not advertised. A length, as in 10.128.0.7/24, routes the rest of that range to the peers answering there. Blank is a valid answer, and a common one: the machine still reaches IPv4 peers, and is reached at its IPv6 address.
Taints
A taint is a compartment label. Inside a realm, taints alone decide which machines can exchange data. The rule is containment, not overlap: two anchors may exchange data only if one of them carries every taint the other does. Try it in the playground.
| A carries | B carries | Exchange data? | Why |
|---|---|---|---|
{x} | {x} | yes | each set contains the other |
{x} | {x, y} | yes | A's set is contained in B's |
{x, y} | {x, z} | no | neither contains the other, despite sharing x |
{x} | {y} | no | neither contains the other |
| none | {x} | no | an anchor with none carries the default compartment, which {x} does not contain |
Everything else still works across a taint boundary: separated anchors still connect, bootstrap from each other and relay for each other. They just cannot exchange data.
- Three machines with three generated taints are three disconnected machines. A taint has to be shared deliberately; nothing propagates it.
- A hub with
{a}and spokes with{a,b}and{a,c}works as a hub. The hub reaches both spokes, and the spokes cannot reach each other. That is the arrangement containment exists to express. - Two machines with
{office,laptop}and{office,desktop}cannot talk, which surprises people. Give them the same set. - Pass
--taintonce per label. A comma-separated list is refused rather than read as one label. A label is at most 64 bytes, with no spaces, and a machine carries at most 32.
conflux status prints the taint, and conflux peers has a DATA column that says yes or no for each peer. When two machines are up and cannot see each other, that column is the first thing to check.
The two modes
The mode is what this machine gets out of the realm. up and proxy are exclusive, because one daemon holds one anchor. The medium is what the anchor reaches the realm over, and either mode works over either medium.
conflux proxy keeps the overlay inside the daemon. Nothing appears on the host, and the only way in is the ports you publish: each connection is handed to a backend you name.
The service stays on loopback, unchanged, and sees every connection arrive from there.
conflux up gives the host a real interface. Anything on the machine can use the overlay, and publishing a service is an ordinary bind.
A client is always a machine running up: a proxy machine has no interface to connect out from.
| conflux up | conflux proxy | |
|---|---|---|
| What the host gets | a TUN interface: ping, ssh, everything | nothing on the host |
| What the realm gets | this machine, at an overlay address | the named services, at overlay ports |
| Privilege to run | root / CAP_NET_ADMIN / Administrator | root, only to register the boot service |
| An IPv4 | yes, owned by the host's kernel | yes, held by the anchor's own stack |
| Needs wintun.dll on Windows | yes | no |
| Can forward a subnet | yes, --subnet | no |
| Can serve or use an internet exit | yes, --serve-exit, --use-exit | no |
| Can run over an uplink | yes, --uplink | yes, --uplink |
| Boot service | yes | yes |
Switching modes keeps the identity, and every setting that does not depend on the mode: the IPv4, the taints, the uplink, the peers, the port and low latency. Moving to proxy clears the subnets and both exits. Moving to up clears the proxy specs.
Reverse proxy
On a machine where CAP_NET_ADMIN is out of reach, skip the interface and publish only the ports that need to be reachable. A peer that connects to this machine's overlay port 8080 gets a fresh connection to 127.0.0.1:3000. Nothing appears on the host, and the anchor itself needs no privilege. sudo is only for registering the boot service.
$ sudo conflux proxy 8080=127.0.0.1:3000 53/udp=127.0.0.1:53
| Spec | Means |
|---|---|
8080=127.0.0.1:3000 | TCP on overlay port 8080 → 127.0.0.1:3000 |
53/udp=127.0.0.1:53 | UDP on overlay port 53 → 127.0.0.1:53 |
5432=[::1]:5432 | an IPv6 backend, bracketed |
80=nas.lan:8080 | a backend on another host the machine can reach, by name |
- The network defaults to
tcpand can betcporudp. The same port on both is two specs,53=…and53/udp=…; a port repeated on one network is refused. - The backend is
host:port, dialled fresh for each connection rather than resolved up front, so a name that does not resolve yet is fine, and so is a named port. It can be loopback or any host this machine can already reach. - The overlay port and the backend port are independent, and nothing is bound on the host, so
80=127.0.0.1:3000clashes with nothing. - With a private IPv4, peers reach these ports at it as well as at the IPv6 address.
To change a running anchor without touching its configuration, use anchorctl's own proxy command. Its changes last until the anchor restarts, when conflux starts it again from the configured list.
$ sudo conflux anchorctl proxy # what it is serving $ sudo conflux anchorctl proxy -add 9000=127.0.0.1:9000 # one more, until restart $ sudo conflux anchorctl proxy -rm 9000 # and gone again
Publishing a service in TUN mode
On a machine running conflux up, the kernel owns the overlay addresses, so a service published on the overlay is an ordinary bind. There is no proxy to configure.
- Bind to
0.0.0.0or::, or to the overlay address itself so the service is reachable only over the overlay. A service bound to127.0.0.1is not reachable from peers. - A service that binds the overlay address at boot should start after conflux. Under systemd,
After=conflux.servicewaits until the anchor is up. - A peer arriving over IPv6 appears from its own derived address. A peer arriving over IPv4 appears from the
198.18.0.0/15range. Write allow-lists against the IPv6 address, which is bound to the peer's identity. - Broadcast and multicast do not cross the overlay, so discovery by mDNS or SSDP does not either. Connect to services by address.
- There are no names for peers, only addresses.
conflux peerslists every peer's, and an entry in/etc/hostsgives one a name of your own.
Subnets, exits and other settings
Forwarding a subnet
conflux up --subnet 192.168.1.0/24 offers that network to the rest of the realm, so machines on it with no conflux at all become reachable. An entry is a private prefix or an interface name, and an interface name follows whatever networks are on it.
$ sudo conflux up --subnet 192.168.1.0/24 --subnet eth1
Exits
$ sudo conflux up --serve-exit # be a way out to the public internet for the realm $ sudo conflux up --use-exit # send this machine's own internet over the overlay
The two are alternatives, both are off unless asked for, and both are up's alone. Conflux never takes either from an enrolment file: an anchor that became an internet exit because a document said so is the worst kind of surprise. --no-serve-exit and --no-use-exit are the way back.
Other settings
| Flag | What it does |
|---|---|
--low-latency | Carry frames as datagrams: no head-of-line blocking between flows, and a lost frame stays lost. For voice, video and games. |
--lan-discovery yes|no|auto | Probe the local network for members of the same tree. Turning it off is a privacy choice, not a connectivity one: it announces that an anchor is here. |
--port N | The UDP port to listen on, for a firewall rule or a port forward. Omit it and the system picks one. |
--interface NAME | The interface name on Linux and Windows. macOS and the BSDs number their own. |
--peers HOST:PORT | Where to start looking for the realm. Enrolment supplies this; name one only to override it. |
Uplink — join over a cable
An anchor normally binds a UDP socket for its transport, so it needs a host IP network underneath. An uplink replaces that: a serial line becomes the medium, and no socket is bound at all.
$ sudo conflux up --uplink /dev/ttyUSB0:115200 --taint brhk-2mq9-tzva-6pjs --no-ipv4 anchor anchor6btpa3gn6w4stipba4hekzho7caw6srfyy5puvbz7mfanaiept5a overlay fd80:c4b9:99ae:4411:c531:fd4f:754f:f08a/48 uplink /dev/ttyUSB0:115200 — no socket bound, no address advertised interface anchor0
Uplinks are Unix only, and one link carries one peer. Conflux watches the device: a link that disappears, or carries nothing for ninety seconds, is reopened for you.
| Line speed | Handshake | |
|---|---|---|
| 115200 baud | about 2 s | comfortable |
| 57600 baud | about 4 s | fine |
| 19200 baud | about 12 s | usable; conflux warns below this |
| 9600 baud | about 25 s | marginal against a 60-second idle timeout |
| LoRa and other duty-cycled radios | hours | no |
The limit comes from the identity model, not the link: post-quantum certificates in both directions are what make a handshake that size.
Configuration
Conflux keeps one configuration per machine, root-owned, with no home directory anywhere in it. Every directory is 0700 and every file 0600.
| Configuration | State, identity and binaries | |
|---|---|---|
| Linux | /etc/conflux/ | /var/lib/conflux/ |
| macOS | /Library/Application Support/conflux/ | /Library/Application Support/conflux/ |
| Windows | %ProgramData%\conflux\ | %ProgramData%\conflux\ |
| FreeBSD, OpenBSD | /usr/local/etc/conflux/ | /var/db/conflux/ |
conflux.json is what you asked for, and the whole of what a reboot needs. Every up and proxy rewrites it.
{
"version": 1,
"mode": "proxy",
"taints": ["brhk-2mq9-tzva-6pjs"],
"ipv4": "10.128.0.5/24",
"proxies": ["5432=127.0.0.1:5432", "80=127.0.0.1:3000"],
"lowLatency": false
}Editing it by hand is supported. sudo conflux start restarts from whatever it says, and a field anchor would refuse is refused by conflux first, naming the field. An export block names where telemetry goes; without one, the machine exports nothing.
Commands
Conflux keeps a small, fixed set of command names for itself. Everything else is passed to anchorctl unchanged: peers, routes, events, metrics, send, inspect, kill and the rest. Run them with sudo: the commands that change the machine need root, and the rest talk to the running anchor through a token only root can read.
| Command | What it does |
|---|---|
conflux up | enrol if needed, start in TUN mode, register the boot service |
conflux proxy | enrol if needed, start in userspace mode serving those backends |
conflux enrol | install a credential a guardian issued, instead of drawing one; --manifest - reads it from stdin |
conflux down | stop the anchor now; the boot service and configuration stay |
conflux start | start it again now, from the configuration already on disk |
conflux renew | fetch a fresh credential and install it on the running anchor, without a restart |
conflux status | conflux's state, with anchorctl status beneath it |
conflux install | register the boot service, and start it if a configuration exists |
conflux uninstall | remove the boot service, the configuration and the identity; --yes skips the question |
conflux version | conflux's version and commit, and the anchor build it carries |
conflux help [COMMAND] | conflux's usage with anchorctl's beneath it, or one command's own usage |
conflux anchorctl … | run the embedded anchorctl, uninterpreted |
Four names exist on both sides: start, proxy, renew and status. Each collision is resolved rather than guessed. Bare conflux status is conflux's and prints anchorctl's beneath it, while conflux status -watch 5s goes to anchorctl because it was given arguments. stop and restart are refused, with a pointer to down and start. Passing them through would leave conflux's configuration describing an anchor that is not the one running.
kill is not what the word suggests
It is one of the passed-through names, and it has nothing to do with conflux down. conflux kill -target ANCHORID -days N stops nothing on this machine. It makes another anchor a target across the whole realm for N days, and it needs a credential issued with -admin. To stop the anchor here, use conflux down. Why that authority needs guarding is covered under security.
Exit codes
| Code | Meaning |
|---|---|
| 0 | success |
| 1 | failure |
| 2 | usage |
| 13 | needs root or Administrator |
| 69 | nothing is running here |
| 70 | the anchor itself would not start |
| 78 | no configuration |
| other | for a passed-through command, anchorctl's own code, verbatim |
Credentials and renewal
On VeilNet's public network, enrolment is one unauthenticated call that takes no body and needs no account. The API mints an identity inline, signs a credential for it, returns both, and forgets them. There is no sign-up, no session and no record. The free tier is free of us, not merely free of charge.
A credential lasts thirty days, and conflux renews it at two thirds of the window, day twenty. That leaves ten days of retry budget for a machine having a bad week, and a failed renewal is retried every minute. Renewal runs on every start, before the anchor is built, and on a timer while it runs. It is hot: the running anchor takes the new credential without dropping a session.
A machine that was switched off past its expiry renews on its next start and keeps its AnchorID and its overlay address. There is no window to miss.
- A failed renewal never falls back to enrolling. That fallback would silently change the machine's overlay address and orphan every peer that knew it.
- Re-running conflux up does not enrol; it reads the identity that is already there.
- Switching modes does not enrol: up after proxy keeps the identity.
On a guardian of your own
A guardian works the other way round. It serves no enrolment route. Each machine is commissioned in advance, its operator is handed one file, and conflux enrol installs it. conflux up then starts from that file without enrolling.
$ sudo conflux enrol --manifest gateway-3.b64 $ sudo conflux up
Renewal authenticates with a secret minted for that one machine, and revocation exists: the guardian can refuse to renew, and its operator can issue a kill order for immediate removal. The guardian also chooses how long a credential lasts, and can allocate the machine's IPv4 and compartment. Enrol copies those into the configuration once, where they can be changed like any other setting. See commissioning a node.
Security
Conflux adds exactly two things the protocol does not have: an executable written to disk, and a private key in a file. The protocol's own guarantees are in the anchor documentation.
Protect the manifest
The manifest holds the 32-byte identity seed, and anyone who has it is that anchor. One issued by a guardian also holds the machine's renewal secret, so whoever has it can keep that identity's credential current as well. Treat it as a private key: keep it off shared storage, out of backups that could leave the machine, and at exactly the permissions conflux sets.
How the file is held
0600, with the mode set on the descriptor before any content is written. Writing first and chmod'ing after leaves a window at the umask's mode.0700on the directory. A0600file in a traversable directory is one rename away from being replaced.- On Windows, where mode bits mean nothing, conflux gives
%ProgramData%\confluxto SYSTEM and Administrators, with a protected DACL that everything beneath it inherits. It refuses a directory another account created. - Never in an argv. It travels on stdin, so it never appears in ps or /proc/*/cmdline. Never in a log either, because the types that carry it redact themselves.
The one command that reaches past this machine
Everything else conflux does stops at the machine it is typed on. conflux kill does not: -target ANCHORID -days N makes another anchor a target across the whole realm for N days. It needs a credential issued with -admin, so an admin credential is not one more copy of a machine's identity. It is the authority to do that to any anchor in the realm. Keep it somewhere that reflects the difference, and issue it to as few machines as the work allows.
- It is half of revocation. Revocation is expiry and kill orders: a kill order removes an anchor within minutes, for N days, and expiry removes it for good once its credential is no longer renewed. On the public network only the first half exists, because renewal there is open to whoever holds the seed: a leaked manifest is a permanent loss of that identity. How the two work together.
- It cannot be called back.
-days Nis chosen once and then waited out, so a mistyped AnchorID takes out a machine that did nothing, for the full N days. Read the target back before running it. - It spreads the way gossip does, peer by peer, so the realm converges on it rather than switching at an instant. A partitioned peer learns late.
- It is unrelated to
conflux down, which stops the anchor on this machine and leaves the registration in place. The two share a connotation and nothing else.
What conflux does not defend against
- A root-equivalent local attacker. They can read the manifest, and that is the end of it.
- A compromised release channel, beyond what the digests printed by conflux version and the release attestation provide.
- Traffic analysis, which belongs to the protocol and is covered in its own documentation.
- Anything a taint appears to promise. Taints limit the blast radius inside a realm whose members are all trusted as members. They are not a tenant boundary.
Troubleshooting
Start with sudo conflux status. It prints conflux's own state and anchorctl's beneath it, and most of what follows is a way of reading that output. Conflux decides the mode, the taints, the IPv4, the subnets, the proxies, the exits, the medium, where the files live and when to renew. The anchor underneath decides everything else: every packet, every route, every credential check. Knowing which of the two is in play usually tells whether a problem is a setting or a network condition.
| Symptom | Where to look |
|---|---|
| Two machines are up and cannot reach each other | Give it a minute; peers take 15 to 60 seconds to find each other. Then the DATA column in conflux peers: it is almost always a taint set that is not contained. |
| A peer is connected, has an RTT, and pings time out | The host's routing table. Nothing is sending overlay addresses to the overlay interface. |
| A machine came back at a different address | The identity was replaced. A second up never re-enrols, so something removed the state directory. |
| conflux status says the credential is EXPIRED | Renewal is failing, and the next line names the error. Renewal needs outbound HTTPS to the API; once it succeeds, the machine keeps its address. |
| conflux refuses to start, naming the clock | The clock is more than ten minutes from the API's. Fix NTP first; nothing connects until then. |
| … needs root | Run the same command with sudo, or from an elevated PowerShell. |
| up stops in a script, asking for an IPv4 | There is no terminal to ask at. Pass --ipv4 ADDRESS or --no-ipv4. |
| conflux status says the binaries are stale | Conflux was upgraded. conflux start restarts onto the new build. |
| Nothing appears on the host | That is proxy mode. up is the one that creates an interface. |
Where the logs are
| Platform | Logs |
|---|---|
| Linux | journalctl -u conflux -n 50 |
| macOS, FreeBSD | /var/log/conflux.log |
| OpenBSD | /var/log/daemon |
| Windows | %ProgramData%\conflux\logs\conflux.log |
When the service starts and the anchor does not, stop the service and run the supervisor in the foreground with sudo conflux serve --foreground. anchord's own lines are prefixed anchord:, so its reason for a refusal is printed in its own words.
Anything this page does not answer is worth an email. We would rather hear it during a pre-release than after: sovereign@veilnet.com.au.