Your files, every device,
no cloud in between.
Syncthing keeps folders identical across your Mac, Ubuntu/WSL, iPhone, and Windows machines by talking device-to-device over TLS — no server, no account, no third party holding your data.
1 · Mental model: a cluster of equals, not a client and a server
Syncthing is not "Dropbox you host yourself." There is no server anywhere — not even optionally. Every device runs the same program, holds a full copy of every folder it shares, and exchanges changes directly with the other devices it trusts. The cluster has no center, so there is nothing to rent, nothing to back up specially, and no single machine whose death takes your data with it.
Four ideas carry everything else in this guide:
- A device is an identity, not an address. Each installation generates a key pair; the device ID is a fingerprint of its certificate. Devices connect only to IDs they have been explicitly told to trust — both sides must add each other (§4).
- A folder is the unit of sharing. You share folder X with devices B and C, folder Y with B only. Every device holds a real, ordinary copy of each folder it participates in — there is no "streaming" or placeholder mode.
- Devices exchange indexes, then blocks. Each device scans its folders, hashes files into blocks, and announces "here is what I have" to connected peers. Peers pull only the blocks they are missing — from whichever connected device has them, torrent-style.
- Change history is per-file version vectors, not timestamps alone. That is how the cluster distinguishes "B has a newer version" from "A and B changed the same file independently" — the latter is a conflict (§8), and no device's opinion outranks another's.
The wrong question: "how do I sync through the cloud?"
The right question is "when are two of my devices awake at the same time?" A cloud service is always awake, so any device can sync at any hour. A Syncthing cluster only moves data while two members are online together. On a desktop fleet this is a non-issue. With an iPhone in the cluster it becomes the central design constraint — iOS will not let Möbius Sync run continuously in the background (§11) — and it is why §10 recommends designating your most-on device as the de-facto hub.
Sources: Getting started · Block Exchange Protocol spec · FAQ
2 · Prerequisite floor
Syncthing asks little. You already run it — the floor here is the vocabulary and shell comfort the labs assume. Check honestly; gaps show up later as "why is this folder red" confusion, not as errors.
Shell — hard
Run commands, edit a text file, understand what a hidden dot-file is, and use mktemp -d-style throwaway directories. Labs run everything in a scratch area.
Networks — soft
Helpful: what a port is, LAN vs internet, why NAT breaks inbound connections. §4 teaches the Syncthing-specific parts (discovery, relays) from zero.
Systemd / launchd — just-in-time
Only needed for autostart on Linux/WSL (§12–13). Introduced there with exact commands; no prior fluency assumed.
Self-assessment
- Can you explain the difference between a file's content and its metadata (mtime, permissions)?
- Do you know why two machines behind different home routers cannot simply open TCP connections to each other?
- Can you find and edit a config file under
~/Library/Application Support(macOS) or~/.config(Linux)?
Two or more checked: proceed. Fewer: proceed anyway — every lab is disposable and touches nothing real — but expect to look things up as you go.
3 · Install & verify on every platform you own
One program, four platforms, four install stories. The binary is the same everywhere; what differs is how it starts at boot and where its config lives. Get the paths into your head now — every troubleshooting session ends up in one of these directories.
macOS — Homebrew (what this guide was verified on)
brew install syncthing
brew services start syncthing # autostart at login via launchd
syncthing --version
syncthing v2.1.3 "Hafnium Hornet" (go1.26.5 darwin-amd64) brew@Sonoma 2026-08-03 21:36:05 UTC [noupgrade]
Where everything lives — from syncthing paths on this Mac, verbatim:
syncthing paths
Configuration file:
/Users/you/Library/Application Support/Syncthing/config.xml
Device private key & certificate files:
.../Syncthing/key.pem .../Syncthing/cert.pem
Database location:
.../Syncthing/index-v2
Log file:
.../Syncthing/syncthing.log
Default sync folder directory:
/Users/you/Sync
key.pem/cert.pem if you ever migrate a machine and want it to keep being "the same device".Ubuntu — including inside WSL
Use the official apt repository, not Ubuntu's own package (which lags). The stable-v2 channel tracks the v2 line, updated the first Tuesday of each month:
sudo mkdir -p /etc/apt/keyrings
sudo curl -L -o /etc/apt/keyrings/syncthing-archive-keyring.gpg https://syncthing.net/release-key.gpg
echo "deb [signed-by=/etc/apt/keyrings/syncthing-archive-keyring.gpg] https://apt.syncthing.net/ syncthing stable-v2" | sudo tee /etc/apt/sources.list.d/syncthing.list
sudo apt-get update && sudo apt-get install syncthing
Config lives in ~/.local/state/syncthing (v2 default on Linux; older installs may still use ~/.config/syncthing). Autostart on Linux is a systemd user service — §12 covers the WSL-specific parts.
Windows — native, three official options
| Option | What it is | Pick it when |
|---|---|---|
| Syncthing Windows Setup | Lightweight official-recommended installer; installs, autostarts, firewall rules | Default choice for a native Windows machine |
Base zip (syncthing.exe) | Bare binary from syncthing.net/downloads; you arrange autostart (Task Scheduler with --no-console --no-browser) | You want full manual control |
| SyncTrayzor v2 | Tray utility + launcher (GermanCoding fork — the original by canton7 is discontinued and breaks with Syncthing 2.x) | You want a tray icon and GUI wrapper |
iOS — Möbius Sync
Third-party App Store client embedding the real Syncthing engine (currently Syncthing 1.28.0 inside Möbius 1.28.0). Free up to 20 MB inside its own sandbox; one-time $4.99 purchase for unlimited sync. It participates in the cluster as a normal device — the deep behavioral differences are iOS's, not Möbius's (§11).
Sources: apt.syncthing.net · syncthing.net/downloads · Autostart docs · Möbius Sync
4 · Devices & identity: trust is explicit, addresses are optional
Everything Syncthing does rests on one security decision made well: a device is its key pair. The 56-character device ID is a fingerprint of a self-signed TLS certificate, generated on first run. Connections are mutual-TLS: each side proves it owns the private key behind its ID, and each side checks the other's ID against its trusted list. No passwords, no accounts, nothing to phish.
syncthing device-id --home=$LAB/home-a # or Actions → Show ID in the GUI
4IT7P52-SRGKLEY-FXEQLAV-ZVFEFJ6-OWZOAFS-V526GMB-B763XFM-T4MWMQF
- Pairing is symmetric. "Two devices will only connect and talk to each other if they are both configured with each other's device ID." Adding B on A produces a pending request on B; B must accept (or pre-add A). One-sided adds do nothing.
- IDs are not secrets. Knowing an ID doesn't let anyone connect — your device ignores IDs it hasn't trusted. Paste them in chat freely.
- Finding peers is a separate problem from trusting them. Trust is the ID list; finding the current IP of a trusted device is discovery, below.
How devices find and reach each other
| Mechanism | What it does | Default | Turn it off when |
|---|---|---|---|
| Local discovery | UDP broadcast on port 21027 announces "ID X is at this LAN address" | On | Never, for a home fleet — it is what makes LAN sync zero-config |
| Global discovery | Devices report their address to Syncthing's public discovery servers; peers look IDs up there | On | LAN-only privacy posture; then set static addresses per device |
| Relays | When no direct path exists (both sides NATed), traffic bounces through a community relay — still end-to-end TLS-encrypted | On | You've arranged port forwarding or Tailscale; relays are slow (last resort by design, priority 50 vs 10 for LAN) |
| Static addresses | Set tcp://host:port on the device instead of dynamic | — | Use for always-known hosts (VPN, LAN server); fastest and most private path |
Sync itself listens on port 22000 (TCP and QUIC over UDP). For two devices on your LAN, nothing needs opening — local discovery plus direct TCP just works. For internet sync without relays, forward 22000 on one side; one reachable member is enough for the pair.
You will run two isolated Syncthing instances side by side, pair them, and watch a folder converge. This sandbox is reused by every later lab — and it is the exact setup used to verify this guide. Your real Syncthing (and its config) is never touched: everything lives under $LAB with discovery, relays, and NAT traversal disabled.
LAB=$(mktemp -d /tmp/st-lab.XXXX) && mkdir -p $LAB/folder-a $LAB/folder-b
syncthing generate --home=$LAB/home-a
... INF Calculated device ID (device=4IT7P52-SRGKLEY-... )
syncthing generate --home=$LAB/home-b
... INF Calculated device ID (device=RLEX6CH-D5B77QK-... )
Isolate the instances (fixed localhost ports, all discovery off). Open $LAB/home-a/config.xml and set — in <gui><address> → 127.0.0.1:8391; replace the three <listenAddress> lines with one tcp://127.0.0.1:22101; set globalAnnounceEnabled, localAnnounceEnabled, relaysEnabled, natEnabled, crashReportingEnabled to false and urAccepted to -1. Same for home-b with ports 8392 / 22102. Then start both:
syncthing serve --home=$LAB/home-a --no-browser --no-restart --no-upgrade > $LAB/a.log 2>&1 &
syncthing serve --home=$LAB/home-b --no-browser --no-restart --no-upgrade > $LAB/b.log 2>&1 &
grep "GUI and API" $LAB/a.log
... INF GUI and API listening (address=127.0.0.1:8391 log.pkg=api)
Pair them and share a folder — the CLI equivalent of "Add Remote Device" + "Add Folder" in both GUIs. Note the symmetry: four commands per side would be needed in the GUI too (add device, accept device, add folder, accept folder):
A=$(syncthing device-id --home=$LAB/home-a); B=$(syncthing device-id --home=$LAB/home-b)
syncthing cli --home=$LAB/home-a config devices add --device-id $B --name lab-b --addresses tcp://127.0.0.1:22102
syncthing cli --home=$LAB/home-b config devices add --device-id $A --name lab-a --addresses tcp://127.0.0.1:22101
syncthing cli --home=$LAB/home-a config folders add --id notes --label "Notes" --path $LAB/folder-a
syncthing cli --home=$LAB/home-a config folders notes devices add --device-id $B
syncthing cli --home=$LAB/home-b config folders add --id notes --label "Notes" --path $LAB/folder-b
syncthing cli --home=$LAB/home-b config folders notes devices add --device-id $A
Within seconds the log shows the handshake:
grep -E "Established|New device" $LAB/a.log | tail -2
... INF Established secure connection (device=RLEX6CH ... connection.crypto=TLS1.3-TLS_AES_128_GCM_SHA256 connection.prio=10 ...)
... INF New device connection (device=RLEX6CH address=127.0.0.1:22102 remote.name=... remote.client=syncthing remote.version=v2.1.3 ...)
- Create a file:
echo "# Meeting notes" > $LAB/folder-a/todo.md. Expect: within ~10 s it appears in$LAB/folder-b/. - Seed the subdirectory later labs use:
mkdir $LAB/folder-a/vault && echo "first idea" > $LAB/folder-a/vault/ideas.md. Expect:vault/ideas.mdappears on B as well — directories sync implicitly with their contents. - Both folders now contain a
.stfolder/directory — the folder marker Syncthing uses to confirm the path is really mounted before it dares sync (deleting it stops the folder with an error instead of propagating an empty disk). - Open http://127.0.0.1:8391 and
:8392— two full GUIs, two devices, one shared folder. Poke around; this is your consequence-free playground.
skipIntroductionRemovals exists for that reason).Sources: Getting started · Firewall docs · Relaying
5 · Folders & folder types: the direction of truth
A folder in Syncthing is an agreement between devices: same folder ID, possibly different local paths, possibly different rules about who may change what. The folder type on each device sets the direction changes are allowed to flow — and choosing types deliberately is what turns "everything syncs everywhere" into a design.
- Folder ID is the cluster-wide name (immutable, e.g.
notesor an auto-generatedabcde-fghij); the label is your local display name (change freely). - Paths differ per device:
~/Noteson the Mac,/home/paul/notesin WSL, sandbox storage on iPhone. Only the ID must match. - Each device applies its own type, watcher settings, ignore patterns (§7), and versioning (§9) — these are all local decisions, not cluster-wide ones.
| Type (per device) | Outbound changes | Inbound changes | Use it for | Red button |
|---|---|---|---|---|
| Send & Receive (default) | Sent | Applied | Normal working copies — your Mac and WSL for notes and docs | — |
| Send Only | Sent | Ignored (tracked, shown as "out of sync") | A reference copy nothing may overwrite — e.g. the Möbius photo export is forced to this type | Override Changes: stomp remote edits with local state |
| Receive Only | Blocked | Applied | Mirrors and backup targets; a replica you want tamper-evident | Revert Local Changes: discard local edits, re-adopt cluster state |
| Receive Encrypted | Originates nothing; re-serves stored ciphertext to peers | Stored encrypted | Untrusted storage node (VPS) that holds ciphertext it cannot read — set an encryption password when sharing | — |
Make instance B a receive-only mirror, edit a file on B behind the cluster's back, and observe that the edit stays local — then revert it.
syncthing cli --home=$LAB/home-b config folders notes type set receiveonly
echo "local edit on receive-only B" >> $LAB/folder-b/todo.md
# force a rescan so the change registers immediately (or wait for the watcher)
BKEY=$(grep -o '<apikey>[^<]*' $LAB/home-b/config.xml | cut -d'>' -f2)
curl -s -X POST -H "X-API-Key: $BKEY" "http://127.0.0.1:8392/rest/db/scan?folder=notes"
curl -s -H "X-API-Key: $BKEY" "http://127.0.0.1:8392/rest/db/status?folder=notes" | grep -o '"receiveOnlyChangedFiles":[0-9]*'
"receiveOnlyChangedFiles":1
- Check A's copy of
todo.md— the edit never arrived. B's GUI shows the folder in the "Local Additions" state with the red Revert Local Changes button. - Revert via REST:
curl -s -X POST -H "X-API-Key: $BKEY" "http://127.0.0.1:8392/rest/db/revert?folder=notes". Expect: B'stodo.mdreturns to the cluster version;receiveOnlyChangedFilesdrops to 0. - Set the type back for later labs:
syncthing cli --home=$LAB/home-b config folders notes type set sendreceive
operations folder-override claims to handle "local for receiveonly", but in this lab it returned silently without reverting anything; the REST /rest/db/revert endpoint (which the GUI button calls) worked. Prefer the GUI button or REST for reverts.Sources: Folder types · Untrusted (encrypted) devices
6 · Cluster simulator: propagation, offline devices, conflicts
Before the deeper mechanics, internalize the rhythm. This simulator models one file (todo.md) across three devices. Version counters stand in for content. Take devices offline, edit on different members, reconnect, and watch convergence — including how a genuine conflict is born. Simplified on purpose: real Syncthing tracks per-file version vectors and syncs at block level; the simulator collapses that to "who has the newest version" plus "did two devices diverge".
Things to try, in order:
- Edit on Mac twice — WSL follows instantly, the iPhone only if its app is "open". That lag is the everyday iOS reality (§11).
- Close the iPhone app, edit on Mac, then open the app: it catches up. Offline devices converge on reconnect; nothing is lost.
- Close the iPhone app, edit on Mac and on iPhone, then open the app: conflict — both edited version 1 independently. The cluster keeps the newer edit under the original name and preserves the loser as a
sync-conflictfile (§8). Nothing is silently discarded.
7 · Ignore patterns: what never enters the cluster
Ignoring is how you keep junk — OS droppings, caches, build artifacts, an Obsidian workspace file that differs per machine — from ping-ponging between devices. The rules live in .stignore at the folder root, and the single most important fact about that file: it never syncs. Each device decides for itself what it refuses to see.
| Pattern | Meaning |
|---|---|
*.tmp | * matches within one path level (no /); this matches any .tmp file in any directory |
build/** | ** also crosses / — everything under any build directory |
/Drafts | Leading / roots the pattern: only Drafts at the folder root |
{cache,tmp} | Alternatives; [a-z] character ranges also work |
(?i)thumbs.db | Case-insensitive match |
(?d).DS_Store | "Deletable": Syncthing may delete this file if it is the only thing blocking a directory deletion — use for all OS junk |
!/Important | Negation: keep syncing this even if a later pattern would ignore it |
// note | Comment. #include otherfile.txt pulls patterns from a file that can sync |
First match wins, top to bottom. Put negations (!) above the broad patterns they punch holes in. One performance caveat from the docs: a non-rooted negation like !foo/bar forces Syncthing to scan inside otherwise-ignored directories; rooted ones (!/foo) don't.
A battle-tested starter for a notes/docs folder synced across macOS, Linux, and iOS:
// OS junk — deletable so it never blocks a folder delete
(?d).DS_Store
(?d)._*
(?d)Thumbs.db
(?d)desktop.ini
// editor / app litter
*.tmp
*~
.obsidian/workspace.json
.obsidian/workspace-mobile.json
.obsidianPlugins and themes syncing across devices is usually what you want, but workspace.json is per-device UI state (open tabs, pane layout) and generates endless noise and conflicts. Ignore the workspace files, keep the rest.printf '*.tmp\n(?d).DS_Store\n' > $LAB/folder-a/.stignore
echo scratch > $LAB/folder-a/scratch.tmp
# wait ~10s for the watcher, then:
ls $LAB/folder-b/
todo.md vault/ # no scratch.tmp — and no .stignore either
scratch.tmpnever reaches B, and B has no.stignore— confirm both.- Now delete the pattern lines from
.stignoreon A. Expect:scratch.tmpsyncs over on the next scan. Un-ignoring makes files simply enter the cluster; nothing is deleted.
Sources: Ignoring files
8 · Conflicts: how simultaneous edits are survived
A conflict is not an error. It is the honest answer to an unanswerable question: two devices changed the same file while out of contact, and no algorithm can know which edit you meant to keep. Syncthing's move is to keep both — one under the original name, one renamed — and let you merge. Understanding exactly which copy wins removes all the fear.
Both experiments below are real transcripts from the Lab 1 cluster (instance A = 4IT7P52, instance B = RLEX6CH). B was stopped, both sides edited todo.md, B restarted:
ls $LAB/folder-a/
todo.md
todo.sync-conflict-20260806-113158-RLEX6CH.md
vault/
cat $LAB/folder-a/todo.md
# Meeting notes
edit from other laptop ← B's edit won the original filename
cat $LAB/folder-a/todo.sync-conflict-*.md
# Meeting notes
edit from Mac ← A's edit preserved in the conflict copy
Read the conflict filename like this:
Repeating the experiment with the edit order reversed (A edited last) produced ideas.sync-conflict-20260806-113306-4IT7P52.md — A's short ID, and A's newer edit kept the original name. So: the more recently modified version wins the filename; the older version becomes the conflict copy; the suffix names the winner's device. The conflict file itself then syncs to every device, so you can merge from anywhere.
- Conflict copies per file are capped by the folder's
maxConflicts(default 10, −1 = unlimited, 0 = disable conflict copies entirely — don't). - Since v2, a deletion can win against an edit — the edited file then survives as a conflict copy rather than silently reappearing.
- Identical changes on both sides (same content) are not conflicts; version vectors merge cleanly.
Living with conflicts in a notes vault
For Obsidian, conflicts concentrate in whatever file you edit on iPhone and Mac in the same afternoon. The workflow that works: search the vault for sync-conflict weekly (or add an Obsidian saved search), diff, merge the lines you want, delete the conflict copy. Prevention beats cure: open Möbius before and after phone editing sessions so the phone is never far behind (§11).
pkill -f "home=$LAB/home-b" # take B offline
echo "edit from A" >> $LAB/folder-a/todo.md
echo "edit from B" >> $LAB/folder-b/todo.md
syncthing serve --home=$LAB/home-b --no-browser --no-restart --no-upgrade >> $LAB/b.log 2>&1 &
# wait ~15s for reconnect + sync, then:
ls $LAB/folder-a/ | grep conflict
todo.sync-conflict-20260806-...-RLEX6CH.md
- Predict before looking: which content is in
todo.md, which in the conflict file? (You edited B second, so B's line should win the name.) - Resolve it: merge the missing line into
todo.md, delete the conflict file. Expect: both actions propagate to the other side within seconds.
maxConflicts deep. Syncthing loses conflicting data only if you set maxConflicts to 0.Sources: Syncing details / conflicting changes · Config reference (maxConflicts)
9 · File versioning: a seatbelt against other devices
Versioning answers one precise question: when another device's change overwrites or deletes a file here, should the old local copy be kept? It triggers only for changes received from the cluster — your own local edits are never versioned. That asymmetry is why versioning is a seatbelt, not a backup: it protects you from your other devices, not from yourself.
| Mode | Keeps | Parameters | Choose when |
|---|---|---|---|
| Trash Can | One previous version per file | cleanoutDays (0 = keep forever) | Minimum viable safety; mimics a recycle bin |
| Simple | N previous versions | keep (count), cleanoutDays | Recommended default — predictable size, real history |
| Staggered | Thinning history: 30-sec grain first hour → hourly for a day → daily for 30 days → weekly until maxAge | maxAge (days, 0 = forever) | Long-lived documents where "last Tuesday's version" matters |
| External | Whatever your script does | command with %FOLDER_PATH% %FILE_PATH% | Piping into your own archive tooling |
Versions land in .stversions/ inside the folder (configurable; keep a custom path on the same filesystem — the docs warn moving files into it may otherwise fail). Real transcript — B had Simple versioning (keep=5) while A edited todo.md and deleted vault/ideas.md:
syncthing cli --home=$LAB/home-b config folders notes versioning type set simple
syncthing cli --home=$LAB/home-b config folders notes versioning params set keep 5
# on A: edit todo.md, delete vault/ideas.md — then on B:
ls -R $LAB/folder-b/.stversions
todo~20260806-113350.md
vault:
ideas~20260806-113400.md
Note the naming: name~YYYYMMDD-HHMMSS.ext. A remote delete also produces a version — the deleted file's last content is stashed. This is your ransomware/fat-finger recovery path: if a compromised or confused device deletes everything, devices with versioning hold the corpses in .stversions.
.stversions, strip the ~timestamp suffix, drop it back into the folder — it syncs like any other change.Sources: Versioning docs
10 · Topology for your fleet: pick where truth concentrates
Your fleet: a Mac (on most of the day), Ubuntu inside WSL on a Windows machine, an iPhone that syncs only when Möbius gets CPU time, and possibly native Windows joining later. The wrong question is "mesh or hub?" — Syncthing is always a mesh; every pair that shares a folder may connect. The right question is: which device is online enough to be the one everyone else converges through?
The two shapes
Full mesh
Every device shares every folder with every other device. Any two online members sync directly. Most resilient; pairing effort grows quadratically; conflict windows shrink because updates take every available path.
Hub-and-spoke
Every device pairs only with one always-on hub (NAS, Pi, home server). One pairing per new device; the hub is always awake to receive and re-serve changes — spokes never need to be online together. The hub is also the natural home for versioning and backups.
| Criterion | Full mesh | Hub-and-spoke |
|---|---|---|
| Needs an always-on device | No | Yes — its whole point |
| Two laptops closed at different times | May never overlap → stale + conflict-prone | Hub bridges the time gap |
| iPhone reality (app rarely awake) | Phone must catch some peer awake | Hub is always there the moment Möbius wakes |
| Pairing effort at N devices | N·(N−1)/2 pairs | N−1 pairs |
| Single point of failure | None | Hub down → sync pauses (data is still everywhere) |
Recommendation for this fleet
Run mesh-with-a-de-facto-hub: pair everything with everything (it's 3–4 devices), but treat the Mac as the hub until you own an always-on box. Concretely:
- Pair all pairs: Mac⇄WSL, Mac⇄iPhone, WSL⇄iPhone. At this fleet size the pairing cost is trivial, and direct WSL⇄iPhone sync is a free bonus when both happen to be awake.
- The Mac — your most-on device — carries versioning (§9) for every folder and is the machine your backups run on. When you open Möbius on the phone, the Mac is the peer most likely to be reachable.
- Point of discipline: edit-heavy folders converge through the most-on device. Finish a phone editing session by opening Möbius while the Mac is awake; that closes the conflict window (§8).
- The upgrade path when you get a Pi/NAS: add it, share all folders to it, mark it Introducer, give it staggered versioning — nothing else changes. Your topology is already hub-shaped; you're just replacing the hub with something that never sleeps.
Translation layer: cloud-sync instincts → Syncthing moves
| Dropbox/iCloud instinct | Syncthing move | Why it's better here |
|---|---|---|
| "It's synced when it's in the cloud" | It's synced when a peer holds it — check the folder's "Up to Date" on both devices | No third party ever holds plaintext; you can verify convergence directly |
| Selective sync to save disk | Share fewer folders with that device; or ignore patterns (§7) | Explicit per-device contracts instead of placeholder magic that needs the network |
| Restore from the website's trash | .stversions on a device with versioning (§9) | Recovery is local and instant; retention is your policy, not a plan tier |
| Share a link with someone | Not Syncthing's job — it syncs your own devices | Honest scope; use email/another tool for handing files to other people |
| "The app is signed in, so it's syncing" | The daemon is running and peers are Connected — check the GUI's device list | Nothing hides behind an account; connection state is the whole story |
| Pay for more space | Buy a disk. Every device holds full copies of what it shares | Cost scales with your hardware, not a subscription |
Sources: FAQ · Relaying · Möbius FAQ (background limits)
11 · iOS: Möbius Sync, honestly
There is no first-party Syncthing for iOS because iOS forbids what Syncthing is: a daemon that runs forever. Möbius Sync embeds the real engine and works well — provided you design around one truth: the phone syncs when the app is open, briefly after, and in occasional background slots iOS grants; nothing more. The Möbius FAQ's own numbers: expect roughly 1–2 hours of total daily sync activity once established.
What that means in practice
- The habit that replaces "it just syncs": open Möbius when you sit down with the phone and when you finish editing. Foreground time is sync time. Everything else is best-effort.
- Two iOS devices syncing directly is near-hopeless — their background windows rarely overlap (the FAQ says to open both apps simultaneously). Another argument for converging through the Mac (§10).
- First sync is slow to start: initial background scheduling can take up to 24 h to establish. Leave the app open in foreground for the first big sync.
- Cellular: Möbius syncs over cellular by default — check its settings if your vault is large and your plan is not.
Where files live on the phone
- Synced folders live in the Möbius sandbox, fully visible in the Files app ("Open Folder" from Möbius, or Files → On My iPhone → Möbius Sync). Obsidian mobile can open a vault there; other apps can "Save to Files" into a synced folder.
- External folders (other apps' sandboxes, via "Pick External Folder") exist but the developers themselves label the feature DANGEROUS — corruption risk when two apps write the same files. Prefer the Möbius-sandbox-as-vault pattern.
- Photos: "Pick Photo Folder" exports collections from the photo library — send-only, one way, deletions propagate. Treat it as "publish camera roll to the cluster", and give the receiving side versioning.
Sources: Möbius Sync FAQ · App Store listing (1.28.0)
12 · WSL & native Windows: one machine, two worlds
A Windows box with WSL gives you two places to run Syncthing, and the right answer depends on where the files live. The rule that decides everything: run Syncthing in the same world as the files it watches. Cross the boundary and you lose change notifications, speed, or both.
Syncing files that live in the Linux filesystem (recommended)
Your setup — Syncthing under Ubuntu/WSL, folders under ~ in ext4 — is the good case: native inotify, native speed. Two WSL-specific operational notes:
- Autostart: modern WSL2 supports systemd. Check
systemctlworks; thensystemctl --user enable --now syncthing.servicegives you the standard Linux setup. Caveat: a user service runs only while WSL itself is running — WSL starts on demand and Windows may terminate an idle distro. If sync should survive "I closed all my terminals", keep a backgroundwslprocess alive (e.g. a Task Scheduler entry at logon runningwsl -d Ubuntu --exec dbus-launch trueor simplywsl ~ -e sleep infinity), or run Syncthing on the Windows side instead. - Reachability: WSL2 is NATed behind the Windows host by default; other LAN devices can't reach port 22000 inside WSL directly, and local discovery broadcasts don't cross the NAT. In practice the WSL instance simply dials out to your Mac (which is reachable), and sync works fine. If you want inbound reachability, newer WSL offers mirrored networking mode; otherwise
netsh interface portproxyforwards 22000 from the host.
Syncing files that live on the Windows side
Do not point WSL Syncthing at /mnt/c/.... Two well-documented problems: changes made by Windows programs on /mnt/c generate no inotify events inside WSL (microsoft/WSL #4739), so Syncthing sees edits only on its hourly full rescan; and 9p filesystem access runs at a fraction of native speed. For Windows-side files, install native Windows Syncthing (§3) — it is a first-class platform with its own filesystem watcher.
| Files live in | Run Syncthing | Watcher | Notes |
|---|---|---|---|
WSL ext4 (~/...) | Inside WSL (apt) | inotify, instant | Your current setup — correct |
Windows NTFS (C:\...) | Native Windows | Windows watcher, instant | Syncthing Windows Setup installer; case-insensitive FS handled |
Windows NTFS via /mnt/c | — avoid — | No change events from Windows apps; slow 9p I/O; hourly-rescan-only sync | |
Sources: Autostart docs · WSL issue #4739 (no inotify across 9p) · Syncthing Windows Setup
13 · Running it for years: GUI, CLI, REST, upgrades
Syncthing is a daemon with three equally real control surfaces: the web GUI on 127.0.0.1:8384, the syncthing cli wrapper, and the REST API both are built on. Learn the mapping once and no interface ever feels magical again.
The three surfaces, same object
| Task | GUI | CLI | REST |
|---|---|---|---|
| Show own ID | Actions → Show ID | syncthing device-id | GET /rest/system/status → myID |
| List folders | left panel | syncthing cli config folders list | GET /rest/config/folders |
| Sync progress | folder card % | — | GET /rest/db/completion?folder=ID |
| Force rescan | folder → Rescan | — | POST /rest/db/scan?folder=ID |
| Pause device | device → Pause | ...config devices ID paused set true | PATCH /rest/config/devices/ID |
| Restart daemon | Actions → Restart | syncthing cli operations restart | POST /rest/system/restart |
REST calls need the API key from Actions → Settings (or <apikey> in config.xml). Real transcript from the lab cluster:
curl -s -H "X-API-Key: $APIKEY" "http://127.0.0.1:8384/rest/db/completion?folder=notes"
{
"completion": 100,
"globalBytes": 66,
"needBytes": 0,
"needItems": 0,
...
}
Housekeeping that matters
- Set a GUI password (Actions → Settings → GUI). The GUI binds to localhost by default, but any local process can otherwise reconfigure your Syncthing — the daemon nags about this for good reason. Required before ever exposing the GUI beyond localhost (prefer an SSH tunnel anyway).
- Watcher + rescan: the filesystem watcher (default on, 10 s settle delay) catches changes instantly; a full scan every
rescanIntervalS(default 3600 s) catches what the watcher missed. On Linux, large folders may needfs.inotify.max_user_watchesraised — one watch per directory; older kernels default to 8192, newer ones scale the default with RAM, so checksysctl fs.inotify.max_user_watchesfirst. - Upgrades: brew/apt handle it on your machines (the brew build shows
[noupgrade]— auto-upgrade disabled, package manager's job). Mixed versions across the fleet are fine (§3). - v1 → v2 memory aid: v2 = SQLite database (first start after upgrade migrates, can be slow), structured log lines like the ones in the labs, deleted-file records forgotten after ~6 months, single-dash long flags removed (
--homenot-home). - Monitoring at a glance: GUI front page — every folder "Up to Date", every device "Connected" or recently seen. That page is the health check; anything red or yellow maps to a row in §15.
Fleet surgery: retiring, moving, renaming
- Retire a device: remove it on each remaining peer (GUI: device → Remove, or
syncthing cli config devices remove --device-id ID). Removal only ends the sync agreement — every device keeps its local files. Do the same on the retired machine if it lives on in another role. - Un-share or remove a folder: removing a folder from Syncthing deletes the agreement and its index, never the files. The now-orphaned copies stay on disk on every former member; delete them manually where unwanted.
- Move a folder to a new path on the same device: pause the folder (or stop the daemon), move the directory whole —
.stfolderincluded — edit the folder's path, resume. The rescan re-hashes, finds everything unchanged, and nothing re-transfers. - Renaming: the label changes freely, per device. The folder ID is immutable — "changing" it means removing the folder and adding a new one, which the cluster treats as a brand-new folder with a full re-index.
Sources: REST API · Config reference · v2.0.0 release notes
14 · Capstone: audit and harden your real fleet
Everything so far ran in the sandbox. The capstone applies it to your production cluster — Mac, WSL, iPhone — as an audit-then-harden pass. Nothing here is destructive; every step is a check followed by an explicit, reversible change. Budget 45–60 minutes with the phone at hand.
Map what you actually have
On the Mac GUI: list every folder, its ID, which devices share it, its type, its versioning. Write it down (three columns: folder → devices → type/versioning). Most long-running setups contain a surprise — an old folder still shared with a retired laptop, or a default ~/Sync nobody uses. Remove dead devices and dead folders now.
Verify the direction of truth
For each folder, ask: should every member be able to change it? Photo export from the phone → the receiving desktop folder can stay send-receive, but consider receive-only if only the phone should originate photos (§5). Notes vault → send-receive everywhere is correct.
Ignore the junk, in the right order
Add the §7 starter .stignore (OS junk + Obsidian workspace files) on every desktop member of the vault folder first, then clean existing synced junk device by device. Confirm afterward: edit a note on the phone, check no workspace.json churn appears in the Mac's recent changes.
Turn on versioning where truth concentrates
On the Mac (§10): Simple versioning, keep=10, on the vault and documents folders. Prove it works: from the phone, edit one note; on the Mac, find the pre-edit copy in .stversions (§9).
Manufacture one real conflict — and resolve it calmly
Airplane-mode the phone, edit the same note on phone and Mac, reconnect, open Möbius. Find the sync-conflict file, read its name aloud (winner's short ID — whose edit won?), merge, delete. You have now done the scariest thing Syncthing will ever do to you, on purpose (§8).
Close the loop on operations
GUI password set on Mac and WSL (§13). WSL: systemctl --user status syncthing healthy, and decide explicitly how WSL stays alive (§12). Phone: confirm the open-Möbius-after-editing habit by checking "Last seen" for the phone on the Mac — it should be today.
Mastery bar: you can draw your cluster from memory — every folder, its members, each member's type and versioning — and predict where a conflict file lands before it happens. If any folder's sharing surprised you in step 1, re-run this capstone after a month.
15 · Troubleshooting: symptom → cause → fix
| Symptom | Likely cause | Fix |
|---|---|---|
| Devices show Disconnected though both online | One side hasn't accepted the other; or discovery can't find an address | Check both GUIs for a pending "Add device?" banner; verify both list each other's exact ID; on static setups check the address field |
| Folder stopped, red "folder marker missing" | .stfolder deleted, or the disk/mount holding the folder isn't there | Restore the mount, or recreate the marker (folder → red text → link) once you're sure the path is right — the marker is the guard against syncing an empty mount as "everything deleted" |
| "Out of sync" items that never clear | Ignore-pattern mismatch between devices (one side ignores what the other announces), or permissions errors | Compare .stignore on both; check the failed-items list on the folder card for exact filenames and errors |
| Changes take an hour to propagate from WSL or Linux | Watcher dead: inotify watch limit exhausted, or files on /mnt/c (no events at all) | Raise fs.inotify.max_user_watches; move the folder into ext4 or run native Windows Syncthing for Windows-side files (§12) |
| Sync crawls though both on same LAN | Peers connected via relay (check device row: "Relayed") | Confirm local discovery on and UDP 21027 not blocked; same subnet; worst case set a static tcp://ip:22000 address |
| Phone perpetually behind | Expecting background magic iOS won't give | Open Möbius deliberately (§11); confirm a desktop peer is awake at those moments (§10) |
| Conflict files piling up in the vault | Editing on two devices within the phone's long sync gaps | End phone sessions by opening Möbius; weekly sync-conflict search-and-merge (§8) |
| "Local Additions" on a receive-only folder | Something wrote into the mirror | Expected tamper flag — review, then Revert Local Changes (GUI/REST; CLI override was a no-op in our v2.1.3 test, §5) |
| Disk filling invisibly | .stversions growing without cleanout | Set cleanoutDays/lower keep; check du -sh .stversions on versioned folders |
| Device identity lost after machine rebuild | New key.pem → new device ID | Restore key.pem/cert.pem/config.xml from backup to resurrect the old identity, or re-pair everywhere (§3) |
Sources: FAQ · Community forum (searchable symptom archive)
16 · Cheat sheet
Universal starting pattern — new device joins the fleet
# on the new device
syncthing device-id # note the ID (or GUI: Actions → Show ID)
# on an existing device's GUI: Add Remote Device → paste ID
# on the new device: accept the pending request; accept each offered folder,
# choosing its local path — then set type, ignores, versioning per role
Paths (macOS · brew)
~/Library/Application Support/Syncthing/ — config.xml, key.pem, index-v2 DB, log
Linux v2: ~/.local/state/syncthing
GUI: 127.0.0.1:8384
Ports
22000 TCP+UDP sync · 21027 UDP local discovery · GUI 8384 localhost. LAN: nothing to open. WAN direct: forward 22000 on one side.
Daily CLI
syncthing device-idsyncthing cli config folders listsyncthing cli config devices listsyncthing cli operations restartsyncthing paths
REST skeleton
curl -H "X-API-Key: KEY" http://127.0.0.1:8384/rest/db/completion?folder=ID · scan/revert via POST (§5, §13)
Special names
.stfolder marker (never delete) · .stignore local ignores · .stversions archived versions · name.sync-conflict-DATE-TIME-WINNER.ext
Ignore starter
(?d).DS_Store · (?d)._* · (?d)Thumbs.db · *.tmp · .obsidian/workspace*.json — first match wins, ! negations on top
Folder types
sendreceive (default) · sendonly + Override · receiveonly + Revert · receiveencrypted (untrusted node)
Versioning quick pick
Simple, keep=10, on the hub/Mac. Staggered for long-lived docs. Remote changes only — never local edits.
Glossary
- BEP — Block Exchange Protocol
- The wire protocol devices speak: index exchange, block requests, over TLS. Unchanged between Syncthing v1 and v2, which is why mixed clusters work. (§1)
- Cluster
- The set of devices that trust each other and share folders. No member is special; there is no server. (§1)
- Conflict / sync-conflict file
- Result of two devices editing the same file while out of contact. Newer modification wins the filename; the older version is preserved as
name.sync-conflict-DATE-TIME-WINNERID.extand syncs everywhere. (§8) - Device
- One Syncthing installation — an identity (key pair), not a machine. WSL and native Windows on one laptop are two devices. (§4, §12)
- Device ID
- 56-character fingerprint of a device's TLS certificate, e.g.
4IT7P52-…. Public; trust requires both sides adding each other's. (§4) - Discovery (local / global)
- How trusted devices find each other's current address: LAN broadcast on UDP 21027, or Syncthing's public lookup servers. Separate from trust. (§4)
- Folder
- The unit of sharing: cluster-wide ID, per-device path, per-device type/ignores/versioning. (§5)
- Folder marker (.stfolder)
- Empty directory proving the folder's path is really present; its absence stops the folder instead of syncing a vanished disk as mass deletion. (§4, §15)
- Folder type
- Per-device direction of truth: send-receive, send-only (Override), receive-only (Revert), receive-encrypted. (§5)
- Ignore patterns (.stignore)
- Per-device rules for what never enters or leaves this copy; never synced itself; first match wins. (§7)
- Index
- A device's database of file metadata and block hashes for a folder, exchanged with peers so each knows what to pull. Stored in the v2 SQLite database (
index-v2). (§1) - Introducer
- A device whose own trusted devices you automatically trust for shared folders — pairing shortcut for hub setups. (§4)
- Möbius Sync
- Third-party iOS client embedding the Syncthing engine (1.28.0). Syncs while foregrounded plus limited background slots. (§11)
- Relay
- Community server that forwards still-encrypted traffic when no direct connection is possible. Slow by design priority; disable only when you have a guaranteed direct path. (§4)
- Rescan / watcher
- Two change-detection layers: instant filesystem watcher (10 s settle) and periodic full scan (default 1 h) as the safety net. (§13)
- .stversions
- Directory holding archived versions (
name~YYYYMMDD-HHMMSS.ext) when versioning is enabled — remote changes and deletes only. (§9) - Version vector
- Per-file record of which device made which change generation; how the cluster distinguishes "newer" from "diverged" without trusting clocks alone. (§1, §8)
- File versioning
- Per-device policy (trash can / simple / staggered / external) archiving the previous local copy when a remote change overwrites or deletes it. (§9)
Index
- API key
- apt repository (stable-v2)
- autostart · macOS (brew services)
- autostart · WSL/systemd
- backup — Syncthing is not one
- BEP
- cellular data (Möbius)
- cluster simulator
- conflict filename anatomy
- conflicts — who wins
- device ID
- discovery, local & global
- folder ID vs label
- folder types
- folder marker missing
- GUI password
- ignore patterns · syntax table
- ignore-then-delete trap
- inotify limits · /mnt/c
- introducer
- key.pem — identity backup
- LAN sync / zero-config
- maxConflicts
- moving a folder to a new path
- Möbius Sync · background limits
- Obsidian on iPhone
- Obsidian workspace.json
- Override Changes (send-only)
- paths (config, DB, log)
- photo export (send-only)
- ports 22000 / 21027 / 8384
- relays
- removing devices & folders
- REST API
- Revert Local Changes (receive-only)
Retrieval quiz
Self-scored flashcards. Answer out loud before revealing. Aim for 13/15 "learned" — misses point you back to the section that teaches it. Score persists locally in this browser.
.stfolder marker protect you from?todo.sync-conflict-20260806-113158-RLEX6CH.md — whose content is inside, whose ID is in the name?.stignore sync between devices? What follows from that?(?d) and a leading / mean in an ignore pattern, and which rule decides between overlapping patterns?(?d): deletable — may be removed if blocking a directory deletion (use for OS junk). Leading /: match at folder root only. First matching pattern top-to-bottom wins. (§7).stversions?/mnt/c?Sources
Checked with syncthing v2.1.3 "Hafnium Hornet" (Homebrew, macOS) · Möbius Sync 1.28.0 · 2026-08-06. All lab transcripts in this guide are real output from a throwaway two-instance cluster on that Mac. External links require network; the guide itself works offline.
- Syncthing documentation — the canonical reference; best used as the follow-up for any section here.
- Getting Started — best for re-walking first-run pairing with the GUI screenshots this text-only guide omits.
- Folder Types — best for the exact semantics of Override/Revert before pressing either red button.
- Ignoring Files — best for full pattern grammar and edge cases beyond §7's table.
- Versioning — best for staggered-mode interval details and external-command templates.
- Firewall Setup — best for port-forwarding and ufw/firewalld one-liners per platform.
- Autostart — best for systemd/launchd/Task Scheduler recipes when provisioning a new machine.
- REST API — best for scripting; every GUI action maps to an endpoint here.
- apt.syncthing.net — best for the current Debian/Ubuntu repo lines (channels: stable-v2, candidate).
- Syncthing releases — best for what changed before upgrading; v2.0.0 notes explain the SQLite/CLI breaks.
- Möbius Sync FAQ — best for iOS background-sync reality, photo export, and external-folder warnings.
- Syncthing Forum — best for symptom-searching odd behavior; the developers answer there.
Printed from the single-file Syncthing Field Guide · verified 2026-08-06 with syncthing v2.1.3.