Scenario

Public Links

Send files to people who will never install anything.

Send a folder of photographs to relatives who have never heard of MeshHold, over an ordinary HTTPS link — and let them send you files back the same way.

The recipient needs no account, no invite and nothing installed. They open a link in a browser and see the files. What they do not do is trust the node serving them: the files are decrypted in their browser, and the node the link points at holds only ciphertext it has no key for.

That is the whole difference from every other file-sharing product, and it is also where the limits come from, so both are on this page.

How it works

Three roles:

  • The holder is a node that has the vault key — your laptop, your NAS. It opens blocks with the vault key and re-seals them under a key belonging to that one link.
  • The gateway is the node with a public address and a certificate. It serves the page and the bytes. It never receives the vault key and cannot read what it serves.
  • The recipient is a browser. It gets the link's key from the part of the URL after the #, which browsers never send to a server, and decrypts everything locally.

The simplest arrangement — and the one to start with — is a single always-on node that is both holder and gateway: a NAS with a forwarded port, or a VPS you already trust with the vault key.

The two-machine arrangement, where the gateway is a rented box you do not trust with the vault key, needs one more thing: the link's content has to be sent to it in advance. That is publishing, and it is worth reading before renting anything, because a link that has not been published is served live by a holder — which means a gateway that holds no vault key has nothing to serve from and will answer nothing at all.

https://share.example.com/s/<token>#k=<key>
                            ▲          ▲
                            │          └─ the key. Never sent to the
                            │             server. Lose it and the link
                            │             cannot be opened by anyone.
                            └─ which link. The server sees this.

Turning it on

The gateway is off by default. In the daemon's config:

node:
  share:
    enabled: true
    listen_addr: ":8443"
    base_url: "https://share.example.com"
    tls:
      mode: acme
      domain: share.example.com

base_url is what links are built from, so it has to be what the outside world can reach — which stops being listen_addr as soon as a proxy or a port-forward is in the path.

On TLS: acme needs the domain to resolve to this node and port 80 open for the challenge. file takes cert_file and key_file. self-signed exists for trying the thing out on a LAN and is useless for a real link — a stranger's browser will refuse it.

If something else terminates TLS — nginx, Caddy, a tunnel — name it:

node:
  share:
    listen_addr: "127.0.0.1:8443"
    trusted_proxies: ["127.0.0.1/32"]

Without that line the gateway sees every visitor in the world as the proxy's own address, and the per-address limit that stops one visitor occupying the listener becomes one budget they all share — the second person to open an album starts being told to slow down. Name only addresses you control: the header is one anybody can set, and it is believed exactly as far as this list says.

From the web UI: open a vault, choose a folder, press Public link.

From a terminal:

meshhold vault link new <vault_id> /Photos/Wedding --ttl 7d --name "Wedding"

The URL is printed once. It is printed once because it exists once: the daemon stores the token's hash and the link's key wrapped under the vault key, so there is nothing to look it up from afterwards. If it is lost, issue a new link.

List and revoke:

meshhold vault link ls <vault_id>
meshhold vault link rm <vault_id> <link_id>

Revoking takes effect on the next request and deletes nothing — the files stay where they are, the link stops working. Any node holding the vault key may revoke any of its links, because a link that leaked has to be killable by whoever noticed.

Receiving files

A drop link is the other direction: people send you files without an account and without seeing anything already there.

meshhold vault link new <vault_id> /Inbox --drop --name "Send us your photos"

Files are encrypted in the sender's browser before they leave, land in Inbox/ in the vault, and go through the same ingest path as anything else you add. A link does one thing — a drop link cannot be read, and a read link cannot be written to — because a drop that could also be read would hand every sender everything the others had sent.

Senders pick as many files as they like — or drag a folder in — and watch a queue: what is done, what is going, what failed and why. Any one of them can be cancelled without touching the rest, and a failed one retried on its own.

Uploads resume. If a connection drops halfway through a large file, choosing it again continues from where it stopped rather than starting over, and the page says how far in it is picking up.

Passwords

meshhold vault link new <vault_id> /Photos --password "…"

The password encrypts the key inside the link, so the URL on its own is useless without it. Send it separately.

It cannot be recovered or changed later, only replaced by a new link — the password is not stored anywhere, which is the same property that makes the link safe. And it is deliberately not available together with a preview card or a plain link, since both publish content the password exists to withhold.

Options worth understanding

Expiry. Every link gets one; the default is a week. "Never" is available and has to be chosen — a permanent public link should not happen by leaving a field empty.

How much it may move. A read link is a bill against your connection and a drop link is a bill against your disk, so both are capped. The default is derived from what is being shared. When the cap is reached the link stops working; nothing is deleted, and raising it brings the link back.

Preview card. Off by default. On, a link pasted into a chat renders as a card with a title instead of a bare URL — at the cost of making that one link distinguishable from the outside, and of the chat app's crawler fetching the preview the moment the message is sent.

Plain links. A private link cannot be fetched with curl, embedded with <img src>, or opened by a television, because the bytes mean nothing without the page's code. --plain trades that away for one file: an ordinary URL anything can fetch, at the price of the node serving it being able to read that file. It never gets the key to the rest of the vault.

A link that has not been published is served live: a holder opens each block with the vault key and re-seals it as the request arrives. It works while a holder is awake and not otherwise, which is fine for a NAS and is the reason a link from a laptop stops working when the lid closes.

Publishing sends the link's content to a gateway ahead of time, sealed under that link's own key. From then on the gateway answers on its own, and the link no longer depends on you being online.

node:
  share:
    publish:
      # Node ids new links are sent to. Empty means live-only.
      gateways: ["12D3KooW…"]
      # Where the gateway stores what others publish to it. Its own
      # directory, never the block store. Defaults under the metadata
      # directory; set it when the disk with room is a different one.
      path: /srv/meshhold/published
      # How much it may hold in total. 0 means only the disk decides —
      # node.blocks_reserve_bytes is respected either way.
      max_bytes: 0

There is no separate switch for accepting what others publish. Turning a node into a gateway is the decision; a gateway that will not store anything is a gateway whose links die the moment their owner sleeps.

In the dialog. Publishing is on when a gateway is configured, and the checkbox says what it does — "keep working when this machine is off" — along with how much room the gateway has. If the folder is larger than that, you are told at the moment of creating the link, and the link is made live-only rather than half-uploaded. A gateway that is merely asleep is not treated as a refusal: the upload starts when it comes back.

Each link then shows where it stands: uploading, with a bar, or "works while you are offline" once the gateway has all of it.

What it costs, said plainly. A published gateway holds ciphertext at rest rather than only passing it through. It still has no key to read it — but a stolen disk is now a stolen copy of the ciphertext, and a password on the link is what makes that difference matter. A plain link is the exception in the other direction: the gateway can read that file anyway, so publishing one means it keeps a readable copy on disk.

Revoking is two facts. The link stops being served by you immediately. It stops being served from a gateway when that gateway receives the revocation and deletes its copy — so a gateway that is offline keeps serving until it hears. The dialog says "revoked here; the gateway has not confirmed it deleted its copy" until one does, and it will not tell you the content is gone from a machine that has not said so.

Drop links are never published: accepting a file needs the vault key, which is exactly what a gateway does not have.

What a recipient can and cannot do

They open a page with the files on it: a grid of previews, or a list with sizes and dates if that suits the share better — their choice, and they can switch. A share of one file skips both and puts that file in the middle of the screen.

Clicking one opens it in place. Pictures, video and audio play without downloading first — seeking works — and PDFs and text open in the same frame. Arrow keys step through the share without closing anything. Everything has a Download button next to it, always, whatever the page can or cannot show.

Download all is one button and gives them a single ZIP. It is built as it goes rather than assembled first, so a share larger than the phone's memory still works, and folders survive into the archive. They can also tick a few files and take only those.

Saved files resume. An interrupted download in a Chromium-based browser continues rather than starting over.

They cannot open the link without JavaScript, and neither can curl. A private link is opaque by construction; that is what stops the gateway from reading it. Files in containers browsers do not handle — MKV, AVI — are offered as downloads rather than played, and the dialog tells you which ones when you make the link, so you hear it before they do.

The page tells them where it is up to while it works: what it is doing, how fast, how long is left. A Details panel shows what has been fetched and whether the server asked it to slow down, which is usually the answer to "why is this taking so long". A How this works panel draws the mechanism — browser, gateway, the machine with the vault key — and is labelled as an explanation, because a browser talks to one address and cannot see past it. Nothing on the page animates a topology it did not measure.

If a link is live-only and the machine holding it is asleep, the page says so — "the machine holding these is offline; nothing has been lost" — rather than reporting a dead link. Expired, revoked and never-existed remain one indistinguishable answer, because telling those apart would turn the gateway into an oracle for which links were ever real.

What this does not claim

This is about ordinary links. A --plain link is read by the gateway by design and none of what follows applies to it.

The gateway never receives your vault key and cannot read what it stores or forwards. But it does serve the JavaScript that decrypts them, so a gateway whose operator is actively hostile could serve modified code that captures the key from a visitor's URL. That is true of every browser-based end-to-end encrypted service — Proton, MEGA, Peergos all share it — and it is worth knowing rather than glossing:

To read your files, the operator of the gateway has to actively serve modified page code — an action, not a capability, and one that can be detected.

If that matters, run the gateway yourself. It is the same daemon.

Related: Core Concepts for what a vault key is and why holding it is the permission.