Syncthing Peer-to-Peer Sync - Continuous Replication Without a Server

Status: Active
Last Updated: 2026-08-26
Category: Cloud - File Synchronization
Prerequisites: linux-fundamentals, docker-compose-intro
Time: 2-3 hours
Tags: syncthing, sync, peer-to-peer, replication, systemd

Summary

Syncthing provides continuous, encrypted, peer-to-peer folder synchronization between devices β€” no central server required. This article covers deploying Syncthing on the homelab server (native systemd and Docker variants), sharing folders between peers, ignore patterns, understanding relay vs direct connections, and writing a hardened systemd unit.

🎯 What You'll Learn

By the end of this article, you'll be able to:


Table of Contents

  1. Where Syncthing Fits
  2. Installation & systemd Unit
  3. Docker Deployment
  4. Folder Sharing & Device Pairing
  5. Ignore Patterns
  6. Relay vs Direct Connections
  7. Versioning

Context / Why This Matters

Syncthing solves a different problem than Nextcloud: there is no web UI, no accounts, no shares-with-links β€” just device-to-device replication where every peer holds a full copy of every shared folder. That makes it ideal for keeping a laptop's working set mirrored to the homelab NAS continuously, which in turn feeds the restic backup pipeline (restic-backups). It also removes Nextcloud from the critical path for bulk data that never needs to be shared via URL.

Important framing: Syncthing is sync, not backup. Delete a file on your laptop, and it is deleted everywhere within seconds β€” including the server copy. That is exactly why it must feed into versioned backups per storage-backup-strategies, and why its built-in file versioning should be enabled.

Implementation / Core Content

Installation & systemd Unit

On Debian/Ubuntu hosts:

curl -fsSL https://syncthing.net/security-key.txt | gpg --dearmor \
  | sudo tee /usr/share/keyrings/syncthing-archive-keyring.gpg >/dev/null
echo "deb [signed-by=/usr/share/keyrings/syncthing-archive-keyring.gpg] https://apt.syncthing.net/ syncthing stable" \
  | sudo tee /etc/apt/sources.list.d/syncthing.list
sudo apt update && sudo apt install syncthing

Upstream ships a unit for user-level operation (syncthing@.service):

sudo systemctl enable --now syncthing@agentic.service
systemctl status syncthing@agentic

The web GUI listens on 127.0.0.1:8384. For remote administration over SSH only:

ssh -L 8384:127.0.0.1:8384 server.fogserv.cloud
# then open http://localhost:8384 locally

If you expose the GUI over the network instead (reverse proxy with TLS per ../security/tls-configuration.md), immediately set an admin username/password and keep GUI β†’ Listen address off 0.0.0.0 unless proxied.

A hardened drop-in override if you prefer a system-level unit:

# /etc/systemd/system/syncthing.service
[Unit]
Description=Syncthing continuous file sync
After=network-online.target
Wants=network-online.target

[Service]
User=syncuser
Group=syncuser
ExecStart=/usr/bin/syncthing serve --gui-address=127.0.0.1:8384 --no-browser
Restart=on-failure
SuccessExitStatus=3 4
RestartForceExitStatus=3 4
# Hardening
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
ProtectHome=read-only
ReadWritePaths=/home/syncuser/.local/state/syncthing /srv/syncthing

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload && sudo systemctl enable --now syncthing

Docker Deployment

For consistency with other fogserv.cloud services (docker-compose-intro):

services:
  syncthing:
    image: syncthing/syncthing:latest
    hostname: fogserver
    environment:
      - PUID=1000
      - PGID=1000
    volumes:
      - ./config:/var/syncthing/config
      - /srv/syncthing:/var/syncthing/data
    ports:
      - "22000:22000/tcp"   # sync protocol (TCP)
      - "22000:22000/udp"   # sync protocol (QUIC)
      - "21027:21027/udp"   # local discovery
      - "127.0.0.1:8384:8384"  # GUI, loopback only; proxy or SSH-tunnel it
    restart: unless-stopped

Run one Syncthing instance per host β€” running both the native package and the container causes index lock conflicts.

Folder Sharing & Device Pairing

Pairing flow (do this once per device pair):

  1. On the server: Actions β†’ Show ID, copy the device ID (a long cryptographic identifier).
  2. On the laptop: Add Remote Device, paste ID, name it fogserver.
  3. The server shows a "New Device" prompt β€” accept, and check Auto Accept only if you trust the peer's folder layout.
  4. On either side: Add Folder (e.g., /srv/syncthing/laptop-docs), set sharing with the remote device.
  5. The other side accepts the folder share and chooses its local path.

Key settings per folder worth changing from defaults:

CLI equivalents exist for headless automation:

syncthing cli show system
syncthing cli config devices add --device-id "<ID>"

Ignore Patterns

.stignore lives at the root of each synced folder and prevents matching files from propagating at all β€” they are neither sent nor tracked.

// .stignore example for a developer laptop folder
*.tmp
~$*
.DS_Store
Thumbs.db
node_modules
.git/cache
target/
dist/
*.iso
.stfolder          // marker dir; ignoring it breaks the folder β€” don't

Syntax essentials:

Gotcha: ignores are per-device configuration, not synced content. If laptop and server disagree on .stignore, you get index divergence and endless rescans. Keep a canonical .stignore checked into git and distribute it.

Relay vs Direct Connections

Syncthing tries, in order: direct TCP/QUIC to discovered addresses, then hole-punched NAT traversal, finally relays (volunteer servers that bounce encrypted traffic).

Check connection quality in the GUI (device row) or:

syncthing cli show connections | jq '.connections[] | {addr: .address, type: .type}'

To prioritize LAN and discourage relays:

Relayed traffic remains end-to-end encrypted (relays see ciphertext only), so privacy is preserved β€” the cost is purely performance.

Versioning

Even with restic downstream, enable per-folder versioning as the first line of defense against sync-propagated mistakes:

Type Behavior
Trash Can Deleted/old files moved to .stversions/
Simple Keep N versions of changed files
Staggered Hourly/daily/weekly aging β€” recommended default
External Call a script per change

Staggered versions live under <folder>/.stversions/ and are excluded from sync by design.

Practical Examples

Example 1: Laptop β†’ NAS mirror feeding restic

# Server side: receive-only folder at /srv/syncthing/laptop-home,
# staggered versioning, 30 days max age.
# Then restic picks it up nightly:
restic backup /srv/syncthing --tag syncthing-mirror

Result: laptop loss or ransomware costs at most seconds of sync lag plus whatever versions/restic retention preserves β€” three independent recovery paths.

Example 2: Verify a healthy direct LAN connection

syncthing cli show connections | jq '.total'
# expect: {"at": "...", "inBytesTotal": ..., "outBytesTotal": ...}
# In GUI: Devices β†’ fogserver-laptop β†’ "Address" should show tcp://192.168.x.x:22000

If you see relay://... on a same-site peer, fix discovery/firewall before syncing large trees.

Example 3: Recover an overwritten document from versions

ls -la /srv/syncthing/laptop-docs/.stversions/report.odt*
cp .stversions/report.odt~2026-08-20-14-33-02 ~/restore-report.odt

Copy it back into the folder and Syncthing redistributes the restored version to all peers.

Troubleshooting & Common Pitfalls

Problem Cause Fix
Peers connect via relay despite LAN UDP 22000/21027 blocked; discovery broken Open firewall ports; add static device addresses
Endless re-scanning / index churn Mismatched .stignore between peers Distribute identical .stignore to all devices
Files owned by wrong UID in container PUID/PGID unset or mismatched Set PUID/PGID env to owning user; chown -R once
Sync storm on start Full rescan of huge tree Raise full-rescan interval; ensure watch enabled
Deletions propagated everywhere instantly No versioning; folder not receive-only Enable staggered versioning; make server side Receive Only
Two instances fighting on one host Native service + container both running Pick one; disable the other permanently
GUI unreachable remotely Bound to loopback by default SSH tunnel or authenticated TLS reverse proxy

Next Steps / Ops Actions

Sources & Related Articles

External references consulted:

Related knowledge-base articles:

Change Log

2026-08-26

Choose Theme

Your selection is saved locally.

Neural Cacophony
Aperture v2
Flux v1
Mosaic Chaos
Nexus v1
Nexus Zest
Prism v2
Synapse