littleMesh

Federating without a public address

An ordinary fediverse server needs a domain, a certificate, and port 443 reachable from the internet. Plenty of people have none of those, and their machine is perfectly good. littleMesh is for them.

The idea

littleMesh lets a littleFedi instance federate from behind home NAT, behind CGNAT, from a laptop, or from a Pi plugged into a router nobody administers.

It is an additional transport, not a new protocol. ActivityPub, WebFinger, HTTP Signatures, media fetches and the delivery queue all run over it unchanged. Nothing above the socket knows the difference between a circuit and a TCP connection.

How a littleMesh circuit is carried Two littleFedi nodes behind NAT connect through a lighthouse, which relays encrypted bytes between them. An optional HTTPS gateway beside the lighthouse gives ordinary fediverse servers a way in. TLS pinned to both node IDs. The relay copies bytes and reads none of them. Node A littleFedi behind NAT Lighthouse relays ciphertext Node B littleFedi behind NAT HTTPS gateway optional, terminates TLS The ordinary fediverse Mastodon, GoToSocial, …
Two mesh nodes reach each other through a lighthouse over a circuit that is encrypted end to end and pinned to both node identities. Nodes that can reach each other directly, on one LAN or where one side has a usable port, skip the relay after the first connection. The dashed path is the optional HTTPS gateway: the only way a non-mesh server can talk to a mesh node, and the only component that can read the traffic passing through it.

The four roles

Only the node and one lighthouse are required.

Who does what in a littleMesh deployment
RoleRequired?Purpose
NodeYes The littleFedi instance behind NAT. Its Ed25519 key is its stable technical identity.
LighthouseYes Relays encrypted circuits. Stores no accounts, holds no deliveries, cannot impersonate a node.
HTTPS gatewayOnly for ordinary fediverse access Gives Mastodon, GoToSocial and other non-mesh servers public HTTPS access to mesh nodes. Normally runs beside a lighthouse.
DirectoryOnly for readable aliases or signed relay replacement Maps a readable alias to a node ID and publishes the node's current pinned lighthouse locators.

Each service can be provided by the littleFedi community or self-hosted. A directory does not replace a gateway, and a gateway does not replace a lighthouse.

Three ways to deploy it

A. Lighthouse only

Use this when every participating server runs littleFedi with littleMesh support. You get NAT traversal and encrypted, node-pinned circuits. Stock Mastodon and GoToSocial cannot reach the instance, and handles contain the full node ID. No gateway, no public certificate, no wildcard DNS, no directory.

[mesh]
enabled = true
suffix = "mesh.littlefedi.social"
lighthouses = [
  "faro01.littlefedi.social:7842@<published-faro01-node-id>",
  "faro02.littlefedi.social:7842@<published-faro02-node-id>",
]
exposure = "federation"

Copy the current lighthouse node IDs from the operator. Never invent or omit a production pin: the pin is what makes the relay untrusted rather than trusted.

B. Lighthouse plus HTTPS gateway

Use this when ordinary fediverse servers must reach the node and a long self-certifying handle is acceptable. The gateway needs wildcard DNS and TLS. The resulting handle looks like @alice@<node-id>.mesh.littlefedi.social, which is honest but not memorable.

C. Lighthouse, gateway and directory

The complete identity model: a readable handle, a technical actor ID that never changes, and a signed, replaceable route.

readable handle  @alice@red-house.mesh.littlefedi.social
technical actor  https://<node-id>.mesh.littlefedi.social/users/alice
current route    signed list of pinned lighthouse addresses

The lighthouses may be community-hosted, self-hosted, or a mixture. The signed directory record tells gateways and mesh callers which relays currently carry the node. Replacing one of them changes neither the handle nor the actor ID.

Names, keys and readable handles

A mesh instance generates an Ed25519 key pair and takes its domain from it: <node-id>.mesh.<lighthouse-host>. Because the name is the public key, reaching that domain means reaching the holder of that key and nobody else. There is no certificate authority in the path and nothing to misissue.

That property is worth the ugly handle, but nobody wants to type it. The opt-in v2 directory maps a readable alias onto the node ID: the visible handle becomes readable while the actor ID stays https://<node-id>.<stable-suffix>/.... The wizard in littlefedi init asks for the alias domain, the directory URL, the stable technical suffix and one to three pinned lighthouses, and prints the exact alias-to-node-ID reservation the directory operator has to approve.

What you are trusting

This is the part to read before deciding, not after.

The lighthouse cannot read your traffic
The payload is a second TLS session pinned to both node IDs. The relay copies bytes. It cannot read them, alter them, or impersonate either side. It can refuse to carry traffic, and it can observe which node IDs talk to each other and when. littleMesh addresses reachability, not anonymity.
The HTTPS gateway can
It terminates TLS for callers who are not mesh members, so its operator can read that traffic, tamper with GET responses, and drop requests. Signed POSTs cannot be altered undetected. Mesh peers never traverse the gateway. If you trust no relay at all, set mesh.suffix = "mesh" and give up interoperability with the ordinary fediverse.
The directory is not a trust anchor
It can assign and resolve an alias and serve signed lighthouse locators, but every record is verified against the node ID and every payload connection still pins that node ID. A compromised directory can censor discovery, or deceive the very first lookup of a human alias. It cannot impersonate a node ID a caller already knows.
The mesh private key is an identity
The key in the mesh_identity table is this instance on the mesh. Protect it and back it up exactly like the ActivityPub actor keys.
The limit worth knowing before you start

A mesh instance federates with other mesh-aware instances out of the box. Mastodon and friends cannot resolve a mesh name at all, and can only reach it if the lighthouse operator runs the optional HTTPS gateway. Nobody is obliged to.

Exposure defaults

Both the gateway and each node default to federation-only exposure. ActivityPub, discovery and public /media/ URLs work; the web interface, login, API and media proxy stay private unless full exposure is explicitly enabled.

Exposure modes, on the node (mesh.exposure) and on the gateway (lighthouse.gateway_mode)
ModeWhat it admits
federationThe default. Federation, discovery and public media only.
fullThe complete instance, web interface and API included.
allowlistFederation for everyone, full access only for the listed identifiers.

The two policies are independent and both apply: the gateway decides what ordinary internet clients may send through it, and the node decides again what it accepts from mesh peers.

Getting started

./littlefedi init      # choose "instance", then answer yes to "Join littleMesh"
./littlefedi --config littlefedi.toml
./littlefedi --config littlefedi.toml mesh id

init can create an instance with either v2 readable aliases or the long-ID fallback, and it can also write a lighthouse configuration if you are the one running the relay. mesh id prints the node identity, mesh peers lists what it is connected to, mesh ping checks a peer, and mesh publish pushes a new signed locator record to the directory.

Replacing a lighthouse

Relays fail, and relay operators stop volunteering. When one goes away, register with the replacement, put both the old and the new pinned locators in [mesh].lighthouses, and publish:

littlefedi --config /etc/littlefedi/littlefedi.toml mesh publish

Do not change mesh.alias, mesh.suffix, local_domain, web_domain, or the mesh identity. Confirm the published sequence increased, then remove the dead locator only after caches have had one record TTL to refresh. The handle and the actor ID must be byte-for-byte identical before and after: that is the whole point of separating the name from the route.

Nodes can register with several pinned lighthouses at once. Failover works when the caller and the target share a backup relay. Lighthouses store no deliveries, so a node that is offline is not a lost post: the normal federation queue retries until it comes back.