Two Devices, Paired
Before anything can sync, each machine needs an identity, and the two identities need introducing. Neither step involves an account, an email address, or anybody's permission.
An identity in three files
$ ./bin/syncthing generate --home=home-a
2026/10/21 02:54:51 INFO: Generating ECDSA key and certificate for syncthing...
2026/10/21 02:54:51 INFO: Device ID: VDKRCPU-UXHNS6G-4QRZCAS-TINLZ4I-UTXP2E6-37KQF7N-JTCRUFC-RRVUGAM
2026/10/21 02:54:51 INFO: Default folder created and/or linked to new config
$ ls home-a
cert.pem
config.xml
key.pem
Three files are the whole device:
key.pem— the private key. This is the device, in the sense that matters: whoever holds it can prove they are this device. Guard it like any private key.cert.pem— the certificate, generated from the key. The device ID printed above is a hash of this certificate, chopped into eight checkable groups. That is why an ID can't be forged or chosen: it is computed, not picked.config.xml— every setting, as XML you can read. Nothing about a Syncthing setup is hidden in a registry or a cloud account; it is this file, and chapter 9 changes it live through the API.
One line of that config does more explaining than most manuals:
$ grep -A 2 '<gui' home-a/config.xml
<gui enabled="true" tls="false" debugging="false" sendBasicAuthPrompt="false">
<address>127.0.0.1:8384</address>
<apikey>Z9ynLX92UTfDTNrLeZPLMbSTxssGips2</apikey>
The API key is generated with everything else. The web GUI and the REST API both live at that address, and the key is the password for both (the GUI also supports setting a user and password; the lab uses the key throughout).
Fresh installs reach out
A generated config is friendly by default — which means it tries to be found:
$ grep -E 'globalAnnounceEnabled|localAnnounceEnabled|relaysEnabled|natEnabled|listenAddress' home-a/config.xml
<listenAddress>default</listenAddress>
<globalAnnounceEnabled>true</globalAnnounceEnabled>
<localAnnounceEnabled>true</localAnnounceEnabled>
<relaysEnabled>true</relaysEnabled>
<natEnabled>true</natEnabled>
Global announcement registers the device ID with public discovery servers so other devices can look it up; local announcement shouts on your LAN; relays offer to carry traffic; NAT traversal tries to open a path through your router. All reasonable on a real laptop. All noise in a lab that wants to watch the engine.
The lab's preparation script turns the four
off, pins the listener to a loopback address,
and names the devices (willow, juniper) —
the same lines afterwards:
<listenAddress>tcp://127.0.0.1:22005</listenAddress>
<globalAnnounceEnabled>false</globalAnnounceEnabled>
<localAnnounceEnabled>false</localAnnounceEnabled>
<relaysEnabled>false</relaysEnabled>
<natEnabled>false</natEnabled>
It also deletes the default folder a fresh
generate sets up pointing at ~/Sync — real
on a real machine, a stray path in the lab.
The introduction
Pairing is mutual and manual: each device must be told the other's ID. There is no "search for nearby users," because with discovery off there is nobody to ask — and even with it on, a device ID is an offer you accept deliberately, not a search result. The lab does it through the API (chapter 9's tools, used early). This object is the whole introduction of juniper, as handed to willow:
{
"deviceID": "PJCM4SS-7WVVBYD-T4GYQC2-MD65W3S-QYLJ7FH-LD3X2JN-ODLDHYY-YKH5RAC",
"name": "juniper",
"addresses": ["tcp://127.0.0.1:22006"],
"compression": "metadata",
"introducer": false,
"autoAcceptFolders": false
}
$ curl -s -X PUT -H "X-API-Key: $KEY" http://127.0.0.1:8385/rest/config/devices/PJCM4SS-... -d @device-b.json
[HTTP 200]
Two fields deserve a stare. "addresses" is a
fixed address here because the lab forbade
discovery; at home this is normally "dynamic",
and discovery fills it in. And
"autoAcceptFolders": false is the security
model in one setting: juniper may offer
willow folders, but willow will not accept one
it wasn't told about. Knowing a device ID gets
you an introduction, never access — both sides
must add each other, and each side chooses
which folders to share.
Start both, and the connection itself reports in:
$ curl -s -X GET -H "X-API-Key: $KEY" http://127.0.0.1:8385/rest/system/connections
{
"peer": "PJCM4SS",
"connected": true,
"address": "127.0.0.1:22006",
"type": "tcp-client",
"crypto": "TLS1.3-TLS_AES_128_GCM_SHA256",
"isLocal": true
}
Read it slowly, because it says a lot: connected, directly, over TLS 1.3 — the encryption is not a setting you find later, it is what the identity is for. Each side verified the other's certificate against the device ID it was given; a stranger with the right IP address and the wrong key would simply fail the handshake.
Check: the API key really is the key. The same request without it:
$ curl -s http://127.0.0.1:8385/rest/system/ping
CSRF Error
[HTTP 403]
And with it, the smallest conversation in the
guide: {"ping": "pong"}.