How to Deploy Syncthing: Self-Hosted Peer-to-Peer File Synchronization

Syncthing is an open-source, continuous peer-to-peer file synchronization system that synchronizes files directly across devices without middleman clouds. Learn how to deploy Syncthing with Docker Compose, configure folders, secure the management interface, and verify peer connectivity.

How to Deploy Syncthing: Self-Hosted Peer-to-Peer File Synchronization

Syncthing is an open-source, continuous, decentralized peer-to-peer file synchronization application. Licensed under MPL-2.0 and built in Go, Syncthing replaces closed, subscription-based cloud storage providers (such as Dropbox, Google Drive, or OneDrive) with an encrypted, sovereign synchronization protocol that streams data directly between your own machines.

Syncthing Dashboard

Key Capabilities and Architecture

  1. Decentralized Peer-to-Peer Synchronization:

Files sync directly between nodes across local area networks (LAN) or over the internet via encrypted relays. There is no central server storing your files, eliminating single points of failure, monthly storage subscription fees, and vendor data harvesting.

  1. Cryptographic Node Identity:

Every Syncthing node generates a unique cryptographic certificate. Machines authenticate via SHA-256 certificate fingerprints (Device IDs), ensuring that only explicitly authorized devices can connect and exchange directory contents.

  1. End-to-End Encryption with BEP:

All communication uses the Block Exchange Protocol (BEP), secured with TLS 1.3 and QUIC over TCP/UDP port 22000. Syncthing also supports untrusted (encrypted) storage nodes, allowing you to replicate files to third-party machines without revealing plaintext contents.

  1. Block-Level Delta Transfers:

Files are partitioned into dynamic hashed chunks (128 KiB to 16 MiB). When a file changes, Syncthing transmits only the modified byte blocks rather than transferring the entire file again over the network.

  1. Zero-Daemon Embedded Storage:

Syncthing maintains its file indexes, version vectors, and synchronization state using an embedded pure-Go LevelDB engine (syndtr/goleveldb) alongside human-readable XML configuration (config.xml), requiring no external database daemons.

  1. Embedded Web Administration GUI & REST API:

A responsive web management dashboard built with AngularJS and Bootstrap is served directly by the standalone Go binary on port 8384, accompanied by a comprehensive REST API for automation and status monitoring.

---

Prerequisites

Before deploying Syncthing, ensure your host environment satisfies the following requirements:

  • Host Operating System: Linux (Ubuntu, Debian, Fedora, Arch, Rocky, Alpine), macOS, or Windows via WSL2.
  • Container Runtime: Docker Engine 20.10+ with Docker Compose v2 (docker compose version v2.20+ recommended).
  • Persistent Storage Volumes: Two dedicated local host directories:

- ./config: Persists configuration (config.xml), node TLS keys, and LevelDB index databases (/var/syncthing/config). - ./data: Represents the container home environment (/var/syncthing) where synchronized folders (e.g. ./data/Sync) reside.

  • Network Ports:

- 3000:8384 (TCP): Web administration GUI and REST API. (Upstream documentation defaults to host port 8384; this guide maps host port 3000 to container port 8384). - 22000:22000/tcp: Block Exchange Protocol (TCP sync traffic). - 22000:22000/udp: Block Exchange Protocol over QUIC. - 21027:21027/udp: Local discovery broadcasts (IPv4 broadcast and IPv6 multicast).

  • Zero Host Toolchains: Go compilers, runtime libraries, and system dependencies are packaged directly inside the official syncthing/syncthing:latest container image.

---

Exact Install Steps

The deployment uses the official multi-architecture image syncthing/syncthing:latest (v2.1.7 "Hafnium Hornet") configured with non-root user permissions, isolated storage volumes, and an integrated HTTP healthcheck.

Step 1: Create the Project Directory and Storage Layout

Create a dedicated project directory and the required configuration and data folders:

mkdir syncthing && cd syncthing
mkdir -p config data/Sync

To verify synchronization right away, seed two demo files into the sync folder:

echo "# Welcome to Syncthing" > data/Sync/Welcome.md
echo "Synced peer-to-peer, no cloud." > data/Sync/sample-notes.txt

Ensure the container user has read and write permissions to both directories:

chmod -R 777 config data

Step 2: Create the Docker Compose Configuration

Create docker-compose.yml in the project root:

cat > docker-compose.yml <<'EOF'
services:
  syncthing:
    image: syncthing/syncthing:latest
    container_name: syncthing
    hostname: syncthing-demo-node
    environment:
      - PUID=1000
      - PGID=1000
    ports:
      - "3000:8384"          # Web GUI (upstream default is 8384:8384)
      - "22000:22000/tcp"    # Sync protocol (TCP)
      - "22000:22000/udp"    # Sync protocol (QUIC)
      - "21027:21027/udp"    # Local discovery
    volumes:
      - ./data:/var/syncthing
      - ./config:/var/syncthing/config
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "curl -fkLsS -m 2 http://127.0.0.1:8384/rest/noauth/health | grep -q OK"]
      interval: 10s
      timeout: 5s
      retries: 6
EOF

Port Configuration: Upstream documentation binds host port 8384 (8384:8384). This deployment uses host port 3000 (3000:8384) to demonstrate custom host port binding. You can use port 8384 or any unallocated host port.

Volume Hierarchy: The compose configuration maps ./data to /var/syncthing and ./config to /var/syncthing/config. Docker mounts the child config volume over the parent data volume cleanly, maintaining separation between system configuration and synchronized payloads.

Step 3: Start the Syncthing Container

Start the service in detached mode:

docker compose up -d

Monitor until the container healthcheck reports healthy:

until curl -s http://localhost:3000/rest/noauth/health | grep -q OK; do sleep 1; done
docker compose ps

Expected output:

NAME        IMAGE                       COMMAND                  SERVICE     CREATED         STATUS                   PORTS
syncthing   syncthing/syncthing:latest  "/bin/entrypoint.sh …"   syncthing   5 seconds ago   Up 4 seconds (healthy)   0.0.0.0:3000->8384/tcp, 0.0.0.0:22000->22000/tcp, 0.0.0.0:22000->22000/udp, 0.0.0.0:21027->21027/udp

Step 4: Verify Container Startup Logs

Inspect the initialization logs to verify cryptographic key generation and service listeners:

docker compose logs --tail 30

Key output markers to look for:

INF Generating key and certificate
INF Default config saved (path=/var/syncthing/config/config.xml)
INF Calculated our device ID (device=XEV2C7T-OGIALYD-ODR4SRY-Z4LIHLP-BHG34ET-KTJ6CVN-PHBI66J-547AMQQ)
INF TCP listener starting (address="[::]:22000")
INF QUIC listener starting (address="[::]:22000")
INF GUI and API listening (address="[::]:8384")
INF Loaded configuration (name=syncthing-demo-node)

Step 5: Configure the Default Sync Folder

In Syncthing v2.1.7 inside Docker, /rest/config/folders starts empty on first boot. You can add your sync folder either through the web UI (by clicking + Add Folder) or via Syncthing's authenticated REST API.

To add the folder programmatically from your host terminal using the auto-generated API key:

KEY=$(grep -oP '(?<=<apikey>)[^<]+' config/config.xml)
ME=$(curl -s -H "X-API-Key: $KEY" http://localhost:3000/rest/system/status | python3 -c 'import json,sys;print(json.load(sys.stdin)["myID"])')

curl -s -H "X-API-Key: $KEY" -H 'Content-Type: application/json' -X POST \
  http://localhost:3000/rest/config/folders \
  -d "{\"id\":\"default\",\"label\":\"Default Folder\",\"path\":\"/var/syncthing/Sync\",\"type\":\"sendreceive\",\"devices\":[{\"deviceID\":\"$ME\"}]}"

The server responds with HTTP 200, indexes the two seeded files in /var/syncthing/Sync, and transitions the folder status to idle.

---

Browser Setup & First Run

With the container healthy, open http://localhost:3000/ in your web browser.

1. Anonymous Usage Reporting Modal

Upon first load, Syncthing prompts you with an opt-in privacy dialog:

Syncthing Anonymous Usage Reporting Modal

Syncthing does not collect telemetry without explicit consent. Click No to keep usage private, or click Yes to share anonymous diagnostic statistics with the Syncthing team.

2. Initial Dashboard Overview & Security Warning

After dismissing the dialog, the main dashboard opens:

Syncthing Dashboard with Security Warning

Two prominent warning banners appear:

  • Red Danger Panel: Warns that Syncthing is currently exposed without web GUI authentication (Syncthing is accessible via web interface from the local network without a password.).
  • Green Callout Panel: Provides a quick shortcut: GUI Authentication: Set User and Password.

3. Inspect the Default Folder

Click on Default Folder in the left column to expand its status panel:

Syncthing Default Folder Details

The expanded panel reveals:

  • Folder Path: /var/syncthing/Sync
  • Global & Local State: 2 files, ~499 B (confirming our seeded Welcome.md and sample-notes.txt are detected)
  • Folder Type: Send & Receive
  • Folder Status Badge: Displays an orange Unshared badge. In Syncthing v2, a folder that has not yet been linked to a remote peer node shows Unshared while remaining fully indexed and idle locally.

4. Rescan Folder Contents

Click the Rescan All button in the top navigation bar:

Syncthing Folder Rescan

Syncthing triggers an immediate index sweep across all configured directories, verifying file hashes against its embedded LevelDB database.

5. Inspect Device Identity and Listeners

Scroll down to the This Device panel (syncthing-demo-node) on the right:

Syncthing Node Device Details

Confirm active node metrics:

  • Device ID: The full cryptographic fingerprint (e.g. XEV2C7T-…). This is the public address you provide when pairing another phone, laptop, or server.
  • Listeners: 3/3 active (local TCP listener, QUIC listener, and relay listener).
  • Discovery: 4/5 active (local broadcast, multicast, and global announcement servers).
  • Version: v2.1.7, Linux Container.

6. Access Settings

Click the Actions dropdown menu in the upper-right corner and select Settings:

Syncthing Actions Menu

The General tab allows editing the Device Name, Minimum Free Disk Space, and copying the API Key:

Syncthing General Settings

Click the Connections tab to inspect NAT traversal (UPnP), global discovery options, and relay facilities:

Syncthing Connection Settings

7. Configure GUI Authentication and Dark Theme

Click the GUI tab to secure the web interface:

Syncthing GUI Authentication Form

  • GUI Authentication User: Enter an administrative username (e.g. admin).
  • GUI Authentication Password: Enter a strong password.
  • GUI Theme: Select Dark from the theme dropdown (options include Default, Dark, Black, and Light).
  • Click Save.

8. Authenticate and Verify the Secured Dashboard

Saving credentials triggers an immediate session lock. The page reloads to the Syncthing sign-in form:

Syncthing Login Screen

Enter your new credentials (admin and your password) and click Log in.

Syncthing Authenticated Dark Dashboard

The dashboard reloads in the Dark theme. The red Danger banner and green password notification are gone, confirming that the web administration interface is secured.

---

Verification Checklist

Run these terminal commands to verify that your Syncthing node is operational and enforcing security policies:

  1. HTTP Healthcheck Endpoint:

``sh curl -i http://localhost:3000/rest/noauth/health # Expected: HTTP/1.1 200 OK, {"status":"OK"} ``

  1. System Version Query (Authenticated):

``sh KEY=$(grep -oP '(?<=<apikey>)[^<]+' config/config.xml) curl -s -H "X-API-Key: $KEY" http://localhost:3000/rest/system/version # Expected: "longVersion":"syncthing v2.1.7 \"Hafnium Hornet\" ... container: true" ``

  1. Database & Folder Synchronization Status:

``sh curl -s -H "X-API-Key: $KEY" 'http://localhost:3000/rest/db/status?folder=default' # Expected: {"state":"idle","localFiles":2,"globalFiles":2,"localBytes":499,"needFiles":0} ``

  1. Authentication Enforcement Probes:

```sh # Unauthenticated query to a protected endpoint must return 403 Forbidden: curl -s -o /dev/null -w "%{http_code}\n" http://localhost:3000/rest/system/status # Expected: 403

# Valid credentials verification: curl -s -o /dev/null -w "%{http_code}\n" -X POST http://localhost:3000/rest/noauth/auth/password \ -H "Content-Type: application/json" \ -d '{"user":"admin","password":"DemoSecretPass123!"}' # Expected: 204

# Invalid credentials rejection: curl -s -o /dev/null -w "%{http_code}\n" -X POST http://localhost:3000/rest/noauth/auth/password \ -H "Content-Type: application/json" \ -d '{"user":"admin","password":"WrongPassword"}' # Expected: 403 ```

  1. Container Lifecycle & Storage Integrity:

``sh docker compose ps --filter "status=running" ls -la config/config.xml config/index-v0.14.0.db ``

---

Troubleshooting

The table below outlines common issues encountered when deploying Syncthing with Docker Compose and their resolutions:

Symptom / ErrorRoot CauseSolution
Folder status badge displays Unshared (orange) instead of Up to DateIn Syncthing v2, a folder that has not been shared with any remote peer device shows Unshared.This is normal behavior for a standalone node. As soon as you add a remote device and share the folder with it, the status updates to Up to Date once synchronization completes.
GET /rest/system/version returns HTTP 403 ForbiddenSyncthing v2 requires an API key for system REST endpoints.Extract the generated API key from config/config.xml (grep -oP '(?<=<apikey>)[^<]+' config/config.xml) and pass it in the X-API-Key HTTP header.
Red "Danger!" banner: "Syncthing is accessible via web interface... without a password"The web management interface does not enforce credentials out of the box.Navigate to Actions ▸ Settings ▸ GUI, specify a username and password, and click Save.
Permission denied when scanning files or saving configThe container process (PUID 1000) lacks write access to ./config or ./data on the host.Adjust directory ownership or permissions on the host system: chmod -R 777 config data or chown -R 1000:1000 config data.
Port conflict: Bind for 0.0.0.0:3000 failed: port is already allocatedAnother service on the host is already bound to host port 3000 or 8384.Change the host port mapping in docker-compose.yml (e.g. "8384:8384" or "8080:8384"), leaving the container target port as :8384.
Remote peers cannot connect over WANRouter or firewall is blocking incoming TCP/UDP traffic on port 22000.Forward port 22000 (TCP and UDP) on your router to the Syncthing host machine, or enable NAT traversal / UPnP in Syncthing connection settings.
Locked out of GUI after setting forgotten passwordLost administrative credentials for web interface.Stop the container (docker compose down), open config/config.xml in a text editor, remove the <user> and <password> elements from <gui>, and restart with docker compose up -d.

---

Teardown & Clean Up

To stop the Syncthing service and remove all container resources:

# 1. Stop and remove containers and network
docker compose down

# 2. To remove containers, volumes, and persistent data completely:
docker compose down -v
rm -rf config data

# 3. Remove project directory
cd .. && rm -rf syncthing

---

Assumptions & decisions

  • Container Image & Version: Used official syncthing/syncthing:latest image, which resolved to v2.1.7 "Hafnium Hornet" (Go 1.27.3).
  • Default Folder Creation Nuance: In the v2.1.7 Docker image, the /rest/config/folders list starts empty upon first boot. While older v1 installations auto-created a Default Folder, v2 requires either a REST API call or using the + Add Folder button in the web UI. Both methods are clearly documented.
  • Folder Badge Status: In Syncthing v2, an unshared folder displays an orange Unshared badge rather than a green Up to Date badge. The post explicitly clarifies this so users know their 2 indexed files are properly stored and ready to share.
  • REST API Authentication: Syncthing v2 requires the X-API-Key header for system REST endpoints such as /rest/system/version. The guide provides a one-liner to extract the API key from config/config.xml.
  • GUI Password & Theme: Demo credentials admin / DemoSecretPass123! and the Dark theme were tested and verified during the Playwright automation session. The guide notes that saving credentials immediately triggers a redirect to the login form.
  • Host Port Binding: Port 3000 (3000:8384) was validated during the deployment run because port 3000 was free on the host. The guide also mentions the upstream default port 8384 for users who prefer standard bindings.
  • Real Browser Screenshots: All referenced screenshot paths (runs/syncthing-20261101-041446/browser/*.png) point to actual captures generated by Playwright during the deployment verification run.
  • Ghost HTML Card Compatibility: The troubleshooting grid is structured as a standard GitHub-flavored Markdown table with header and delimiter rows, ensuring that ghost-draft's mdToHtml parser converts it into a Ghost HTML card (ghostHtmlCard).