<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Syncthing – Serverküche</title><link>https://serverkueche.de/en/tags/syncthing/</link><description>Syncthing – Neueste Beiträge von Serverküche</description><generator>Hugo</generator><language>en-US</language><managingEditor>feedback@serverkueche.de (Serverküche)</managingEditor><webMaster>feedback@serverkueche.de (Serverküche)</webMaster><copyright>2026 Serverküche</copyright><lastBuildDate>Tue, 08 Sep 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://serverkueche.de/en/tags/syncthing/index.xml" rel="self" type="application/rss+xml"/><item><title>Syncthing: Sync Files Between Devices – Without the Cloud</title><link>https://serverkueche.de/en/tutorials/syncthing-sync-files/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><author>feedback@serverkueche.de (Serverküche)</author><guid>https://serverkueche.de/en/tutorials/syncthing-sync-files/</guid><description>Sync folders directly between your devices, encrypted and without a third-party cloud: Syncthing as peer-to-peer sync behind Traefik, with backup tips.</description><content:encoded><![CDATA[<p>Dropbox without Dropbox: Syncthing syncs folders directly between your devices – encrypted, without the files ever sitting on someone else&rsquo;s server. On an always-reachable VPS your server becomes a reliable sync node that mediates even when your laptop and phone are never online at the same time.</p>
<h2 id="what-are-we-building">What are we building?</h2>
<p>An always-on Syncthing node with <strong>Syncthing v2.1.2</strong> behind <a href="/en/tutorials/traefik-reverse-proxy/">Traefik</a>. By the end you reach the web interface encrypted under your domain, have secured it with a password and can pair your devices via their <strong>device IDs</strong> so that selected folders sync automatically. Unlike Nextcloud, Syncthing isn&rsquo;t central storage but a <strong>peer-to-peer sync</strong>: every device holds a full copy, and the server is just a particularly reliable participant.</p>
<h2 id="prerequisites">Prerequisites</h2>
<ul>
<li>A server with <a href="/en/tutorials/traefik-reverse-proxy/">Traefik</a> running and <a href="/en/tutorials/docker-compose-basics/">Docker Compose</a></li>
<li>A <a href="/en/tutorials/connect-domain-to-server/">subdomain pointing at the server</a> – <code>YOUR_DOMAIN</code> below</li>
<li>The ability to open port <strong>22000</strong> (TCP+UDP) in the <a href="/en/tutorials/firewall-ufw-setup/">firewall</a> – that&rsquo;s where the actual sync runs</li>
<li>Syncthing on at least one other device (desktop, or the Android app)</li>
</ul>
<div class="not-prose my-6 overflow-hidden rounded-xl border border-paprika-200 bg-paprika-50 dark:border-paprika-800 dark:bg-paprika-900/20"
     data-track-content data-content-name="Affiliate-Box · /en/tutorials/syncthing-sync-files/" data-content-piece="VPS 1000 G12">
  <div class="flex items-center justify-between border-b border-paprika-200 bg-paprika-100 px-4 py-1.5 text-xs font-semibold uppercase tracking-wide text-paprika-700 dark:border-paprika-800 dark:bg-paprika-900/40 dark:text-paprika-300">
    <span>🍳 Recommendation</span>
    <span title="Links marked with * are affiliate links.">Ad</span>
  </div>
  <div class="flex flex-col gap-4 p-4 sm:flex-row sm:items-center sm:justify-between">
    <div>
      <p class="text-lg font-bold text-slate-900 dark:text-white">VPS 1000 G12</p>
      <p class="mt-1 text-sm text-slate-600 dark:text-slate-300">4 vCores · 8 GB RAM · 256 GB NVMe</p>
      <p class="mt-1 text-sm font-semibold text-paprika-700 dark:text-paprika-400">from €10.36/month</p>
      <p class="mt-2 text-sm text-slate-600 dark:text-slate-400">A small VPS as an always-on sync node is plenty – Syncthing is frugal.</p>
    </div>
    <a href="https://www.netcup.com/en/server/vps/vps-1000-g12-iv-12m?ref=44083" rel="sponsored noopener" target="_blank"
   data-track-event="Affiliate|netcup: Affiliate-Box|VPS 1000 G12 · {page}"
   class="inline-flex shrink-0 items-center justify-center rounded-lg bg-paprika-600 px-5 py-2.5 font-semibold text-white transition-colors hover:bg-paprika-700">
  Go to netcup →
</a>

  </div><div class="px-4 pb-4"><p class="not-prose my-3 flex flex-wrap items-center gap-x-2 gap-y-1 rounded-lg border border-herb-500/40 bg-herb-50 px-3 py-2 text-sm text-slate-700 dark:bg-herb-900/20 dark:text-slate-200">
  <span>💶 <strong>5 € voucher</strong> for new netcup customers:</span><code data-track-voucher="36nc17844976032"
        class="rounded bg-white px-2 py-0.5 font-mono text-sm font-semibold text-herb-800 dark:bg-slate-800 dark:text-herb-400">36nc17844976032</code>
  <span class="text-xs text-slate-500 dark:text-slate-400">(new customers only, no domains)</span>
</p></div>
</div>

<h2 id="step-by-step">Step by step</h2>
<h3 id="step-1-create-the-directories--and-set-the-permissions-right-away">Step 1: Create the directories – and set the permissions right away</h3>
<p>Syncthing stores its configuration and the synced data in two folders. The container runs as a normal user (UID 1000), so the folders must belong to that user – this is the most common startup trap:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">mkdir -p /opt/syncthing/config /opt/syncthing/data
</span></span><span class="line"><span class="cl">chown -R 1000:1000 /opt/syncthing/config /opt/syncthing/data
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> /opt/syncthing</span></span></code></pre></div>
</div>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-amber-400 bg-amber-50 dark:border-amber-700 dark:bg-amber-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">⚠️</span>Without chown, Syncthing won&#39;t start
  </p>
  <div class="prose-kitchen text-sm">If the folders belong to root (the default for freshly created directories), Syncthing can&rsquo;t write its certificate and configuration and enters a restart loop (&quot;<code>permission denied</code>&quot; in the log). The <code>chown</code> to UID/GID 1000 before the first start prevents this – more on permissions in <a href="/en/tutorials/linux-users-and-permissions/">Users &amp; permissions</a>.</div>
</div>
<h3 id="step-2-the-compose-file">Step 2: The Compose file</h3>
<p>Replace <code>YOUR_DOMAIN</code>. The important thing is the difference between the web interface and the sync traffic:</p>
<div class="sk-code">
  <span class="sk-code-head">YAML</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">syncthing</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">syncthing/syncthing:2.1.5</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">hostname</span><span class="p">:</span><span class="w"> </span><span class="l">koch-server</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">PUID</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;1000&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">PGID</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;1000&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">./config:/var/syncthing/config</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">./data:/var/syncthing/Sync</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;22000:22000/tcp&#34;</span><span class="w">   </span><span class="c"># sync protocol (direct, not via Traefik)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;22000:22000/udp&#34;</span><span class="w">   </span><span class="c"># sync over QUIC</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;21027:21027/udp&#34;</span><span class="w">   </span><span class="c"># local device discovery</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">networks</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">proxy]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">labels</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;traefik.enable=true&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;traefik.http.routers.syncthing.rule=Host(`YOUR_DOMAIN`)&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;traefik.http.routers.syncthing.entrypoints=websecure&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;traefik.http.routers.syncthing.tls.certresolver=le&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;traefik.http.services.syncthing.loadbalancer.server.port=8384&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">networks</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">proxy</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">external</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span></span></span></code></pre></div>
</div>
<p>Only the <strong>web interface</strong> (port 8384) runs through Traefik – that&rsquo;s what the labels handle. The actual <strong>sync traffic</strong> (port 22000) is its own protocol, not HTTP, so it&rsquo;s published directly via <code>ports:</code>, not routed through Traefik. This separation is exactly what <a href="/en/tutorials/understanding-docker-networks/">Understanding Docker networks</a> explains.</p>
<h3 id="step-3-open-the-firewall-and-start">Step 3: Open the firewall and start</h3>
<p>Open the sync port in the firewall (otherwise the devices won&rsquo;t find each other):</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">ufw allow 22000/tcp
</span></span><span class="line"><span class="cl">ufw allow 22000/udp</span></span></code></pre></div>
</div>
<p>Then start:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker compose up -d</span></span></code></pre></div>
</div>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-amber-400 bg-amber-50 dark:border-amber-700 dark:bg-amber-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">⚠️</span>Patience: Traefik waits for the health check
  </p>
  <div class="prose-kitchen text-sm">The Syncthing image ships its own health check, and <strong>Traefik only routes to a container once it&rsquo;s <code>healthy</code></strong>. But the first check only runs after the default interval of 60 seconds – until then you get a Traefik 404 via the domain. That&rsquo;s normal; just wait. Check with: <code>docker inspect -f '{{.State.Health.Status}}' syncthing-syncthing-1</code>.</div>
</div>
<p>Once the status is <code>healthy</code>, the interface responds with valid HTTPS:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">curl -sI https://YOUR_DOMAIN/ <span class="p">|</span> head -1</span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">HTTP/2 200</span></span></code></pre></div>
</div>
<h3 id="step-4-the-web-interface-and-the-crucial-security-step">Step 4: The web interface and the crucial security step</h3>
<p>Open <code>https://YOUR_DOMAIN/</code>. The interface is in English by default; you see your device (&ldquo;This Device&rdquo;) with its <strong>device ID</strong>, version and status:</p>
<p><figure class="my-6"><img src="/en/tutorials/syncthing-sync-files/st-dash_hu_6f98ec301e3c00dc.webp" srcset="/en/tutorials/syncthing-sync-files/st-dash_hu_a5e034160132168e.webp 480w, /en/tutorials/syncthing-sync-files/st-dash_hu_6f98ec301e3c00dc.webp 768w, /en/tutorials/syncthing-sync-files/st-dash_hu_91453e6c0e68b627.webp 1200w, /en/tutorials/syncthing-sync-files/st-dash_hu_ef3da816727f01d0.webp 1920w" sizes="(min-width: 768px) 768px, 100vw"
    width="768" height="432"
    data-full="/en/tutorials/syncthing-sync-files/st-dash_hu_cdb9c4550e2e8a0e.webp"
    alt="The Syncthing web interface with your own device koch-server, version v2.1.5 and the device ID" title="The web interface: this device with its device ID, and below it sections for folders and remote devices"
    loading="lazy" decoding="async" class="rounded-lg"><figcaption class="mt-2 text-sm text-center text-slate-500 italic">The web interface: this device with its device ID, and below it sections for folders and remote devices</figcaption></figure></p>
<p>On a fresh install Syncthing warns at the top with a red banner that no <strong>GUI password</strong> is set yet – and that&rsquo;s serious: without a password, <strong>anyone</strong> who reaches the interface can access your files. So under <strong>Actions → Settings → GUI</strong> immediately set a username and a strong password. After that Syncthing asks for a login on every access – on top of Traefik&rsquo;s HTTPS.</p>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-amber-400 bg-amber-50 dark:border-amber-700 dark:bg-amber-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">⚠️</span>Set the GUI password in the interface, not via generate as root
  </p>
  <div class="prose-kitchen text-sm">Set the password through the web interface. Running <code>syncthing generate</code> <strong>as root</strong> inside the container instead writes <code>config.xml</code> with the wrong owner – after that Syncthing won&rsquo;t start (<code>permission denied</code>). If it happens: <code>chown -R 1000:1000 /opt/syncthing/config</code> and restart.</div>
</div>
<h3 id="step-5-pair-a-second-device">Step 5: Pair a second device</h3>
<p>Syncthing connects devices via their <strong>device ID</strong> – a long, unique key. Pairing is mutual: each device has to know the other. Click <strong>Add Remote Device</strong>:</p>
<p><figure class="my-6"><img src="/en/tutorials/syncthing-sync-files/st-adddevice_hu_18cb814332b0d548.webp" srcset="/en/tutorials/syncthing-sync-files/st-adddevice_hu_95372ddb4f9d61c.webp 480w, /en/tutorials/syncthing-sync-files/st-adddevice_hu_18cb814332b0d548.webp 768w, /en/tutorials/syncthing-sync-files/st-adddevice_hu_d1d1cd6da4310c8b.webp 1200w, /en/tutorials/syncthing-sync-files/st-adddevice_hu_ef6b04236045c490.webp 1920w" sizes="(min-width: 768px) 768px, 100vw"
    width="768" height="432"
    data-full="/en/tutorials/syncthing-sync-files/st-adddevice_hu_e3a64c42b986adaa.webp"
    alt="The &ldquo;Add Device&rdquo; dialog with fields for device ID, name and group" title="A device is added by its device ID – you find the ID on the other device under \&#34;Actions → Show ID\&#34;"
    loading="lazy" decoding="async" class="rounded-lg"><figcaption class="mt-2 text-sm text-center text-slate-500 italic">A device is added by its device ID – you find the ID on the other device under \&#34;Actions → Show ID\&#34;</figcaption></figure></p>
<p>You find the other device&rsquo;s ID there under <strong>Actions → Show ID</strong> (as text and QR code). Enter it here, give it a name and save. On the <strong>other</strong> device you repeat this with your server&rsquo;s ID. Once both sides know each other, the device shows as &ldquo;connected&rdquo;.</p>
<h3 id="step-6-share-a-folder">Step 6: Share a folder</h3>
<p>Now you define <em>what</em> gets synced. Via <strong>Add Folder</strong> you choose a path (on the server e.g. <code>/var/syncthing/Sync/documents</code>, which corresponds to the mounted <code>./data</code> folder) and give it a <strong>folder ID</strong>. Under the <strong>Sharing</strong> tab you tick the devices to sync the folder with. On the other device you confirm the incoming share – from then on Syncthing keeps both sides identical automatically.</p>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-herb-400 bg-herb-50 dark:border-herb-700 dark:bg-herb-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">🧑‍🍳</span>File versioning as protection against mistakes
  </p>
  <div class="prose-kitchen text-sm">Enable <strong>file versioning</strong> per folder in its settings (e.g. &ldquo;Simple Versioning&rdquo;, 5 versions). If one device deletes or overwrites a file, the old version is kept on the other devices in a <code>.stversions</code> folder. Important: this protects against mistakes, it&rsquo;s <strong>not a backup</strong> (see below).</div>
</div>
<h2 id="when-things-go-wrong">When things go wrong</h2>
<div class="troubleshoot not-prose">
<p><strong>The container won&rsquo;t start / restart loop with <code>permission denied</code>.</strong> The permissions on the <code>config</code> or <code>data</code> folder are wrong. <code>chown -R 1000:1000 /opt/syncthing/config /opt/syncthing/data</code> and restart (see the warning in step 1).</p>
<p><strong>The interface gives a Traefik 404 even though the container is running.</strong> The health check isn&rsquo;t <code>healthy</code> yet – Traefik deliberately doesn&rsquo;t route then. Wait up to 60 seconds after start and check the status (see the warning in step 3).</p>
<p><strong>Two devices won&rsquo;t connect.</strong> Check three things: is port 22000 (TCP+UDP) open in the firewall? Did you enter the ID <strong>on both</strong> devices (pairing is mutual)? And is the ID exact (typos are caught easily thanks to a built-in checksum)? A &ldquo;Disconnected&rdquo; state with correctly entered IDs almost always points to the firewall port.</p>
<p><strong>A folder is stuck at &ldquo;Syncing 0%&rdquo;.</strong> Usually the share confirmation is missing on the other side, or the folder ID doesn&rsquo;t match – it must be <strong>identical</strong> on both devices. Also check the write permissions in the target folder.</p>
<p><strong>No access after setting the GUI password.</strong> Forgot the password? You can reset it in <code>config.xml</code> (<code>&lt;gui&gt;</code> block) by removing the <code>&lt;user&gt;</code> and <code>&lt;password&gt;</code> lines and restarting Syncthing – then the interface is reachable without a login again (and you set it anew immediately).</p>

</div>

<h2 id="maintenance--backups">Maintenance &amp; backups</h2>
<ul>
<li><strong>Updates.</strong> Occasionally bump the image tag (<code>syncthing/syncthing:2.1.5</code>) to the current version and <code>docker compose up -d</code>. Syncthing stays compatible within a major version; when jumping to a new major (like v2 here) read the release notes first. Your normal <a href="/en/tutorials/keep-docker-stack-updated/">update process</a> handles the rest.</li>
<li><strong>Backup is mandatory – sync is not a backup.</strong> This is the most important point: Syncthing <em>mirrors</em> changes, including accidental deletion and ransomware encryption. So back up the sync folder additionally with a real, versioned backup that the sync can&rsquo;t reach – e.g. <a href="/en/tutorials/restic-backups/">Restic</a> on the server. The <code>config</code> folder (device IDs, folder shares) belongs in the backup too.</li>
<li><strong>Honest effort.</strong> Once set up, Syncthing runs with very little maintenance. Keep an eye on disk space (<code>df -h</code>) when syncing large folders with versioning, and prune old <code>.stversions</code> folders occasionally.</li>
</ul>
]]></content:encoded></item></channel></rss>