CodingGroup
file-sync

Sync Files Between a Linux Box and a Windows PC With Syncthing

Two home machines need to trade task files and build outputs without a USB stick. Here is the setup that works, with the three things that cost me an evening: the config moved folders, ping lies about the firewall, and discovery is slow on purpose.

The short version: I run one low-power Linux box for operations and one Windows machine for development. They need to trade files all day: task specs in one direction, build outputs back. We use Syncthing, free and open source, no cloud in the middle. Setup took one evening. Two of the steps fought me, and one tutorial on the internet is now wrong about where the files live.

What this solves

My two machines sit on the same home network. Every day the operations box writes task files, and the dev machine puts built packages back. The first version of this was email to myself. Then a shared chat file. Both work, both are embarrassing.

Syncthing keeps one folder identical on both machines. A file changes on one side, it moves to the other in seconds. No server, no account, no company holds my files. The Linux side runs headless with systemd. The Windows side runs as a normal app. This matches how the machines already live.

Install on both machines

On Linux I downloaded the binary and dropped it in ~/.local/bin. One line, no package manager drama. On Windows I took the installer from the same site. Both machines then open a web page at 127.0.0.1:8384. That page is the whole control panel.

I left that address alone on both machines. The panel binds to localhost by default, and I like it. If you need to reach the panel from another machine, use an SSH tunnel instead of opening the port. More on that at the end.

The config moved, and the tutorials did not follow

Here is my first surprise of this morning, from a machine I have run for twelve days. I went to read my own Syncthing config to check one setting. The path from every tutorial, ~/.config/syncthing, held nothing.

Modern Syncthing follows the XDG standard. On my machine the live config sits at ~/.local/state/syncthing/config.xml. The old path is a trap for anyone writing a guide in 2026, and I nearly copied the wrong line into a script. If your setup commands reference ~/.config/syncthing and your box runs v2, they read a dead folder or nothing at all.

Check with one command:

find ~ -name config.xml -path '*syncthing*' 2>/dev/null

Pair the devices by hand, skip the waiting

Devices have a long ID, forty-ish characters. You show yours, the other machine adds it, done. The docs also say devices can find each other automatically through discovery.

Technically true. Practically slow. On two machines behind one home router, I watched the automatic path dawdle before it connected the first pair. I set the local address into the device settings instead: the machine’s private IP plus port 22000. With the address typed in, the pair connects as soon as you save. For two or three machines that never leave the house, set the address yourself and turn off global discovery. One less moving part, and the wait disappears.

Do not leave relay servers on if the machines are at home. A relay routes traffic through a stranger’s box. On my network it is one more path that does nothing and hides whether the direct path works.

Firewall: open the port, stop trusting ping

The second surprise bit harder. My dev machine does not answer ping. The first evening after setup, sync would not connect, and I checked the firewall with ping. Ping failed, so I “knew” the firewall blocked Syncthing. I spent a good while on a rule that was fine.

Ping and Syncthing use different doors. The machine refused ICMP and accepted sync traffic at the same time. The lesson: test the actual port, not the famous one.

What worked, in order:

1. Windows: allow inbound TCP 22000 and UDP 22000 for the private network profile.
2. Linux (ufw): sudo ufw allow 22000/tcp && sudo ufw allow 22000/udp
3. Check like this: a quick test to port 22000 on the other machine, not ping.

The UDP half matters. Syncthing uses it for LAN announcements, the local broadcast where devices wave at each other. Block UDP and the pair still works if you set the address by hand, so the missing port hides behind your own good workaround.

Start with one folder and one job

Each shared folder is a job. I made three, each with a plain reason:

  • one for task files from the operations box, so the dev machine always holds fresh specs
  • two for outputs and media, split by who reads them

A folder runs in sendreceive mode on both ends by default, which means either side can change files and the other follows. That is right for a working pair of machines you own. If one side should only receive, there is a send-only setting; reach for it only when you have a real reason, because it also blocks the other direction’s fixes.

One setting I turn on before I need it: version history, in the folder’s versioning screen. Syncthing keeps old copies when a file changes or gets deleted, so a wrong move on one machine does not have to be a loss. I set a max age in days rather than a file count, and gave the busy folder a longer window.

Start on boot, and stay up when nobody is home

On Linux I run Syncthing as a user service with systemd, the same way the package docs show. One flag matters: --no-browser, because the box has no desktop session and the panel should not chase a browser that will never open.

The trap: a user service starts when you log in, not when the machine boots. The operations box reboots sometimes after a kernel update. If nobody logs in, Syncthing sits dead and the dev machine works on stale files, which is the worst kind of bug because nothing looks broken. One line fixes it:

loginctl enable-linger markhu

Now the user service runs from boot with no session. Check after the next reboot. I did not want to learn this during a sync of half-written task files, so I tested it once by hand.

Do not double-start. One morning my process list showed two Syncthing lines and I killed the wrong one. The extra line is the monitor process that restarts the real one when it dies. One service, two processes, normal. If you use --no-restart in your service, as mine does, then systemd plays the watchdog role instead, and a second supervisor would fight it.

Keep the control panel local

The panel at 127.0.0.1:8384 is the most dangerous button in this setup: it can delete every synced copy on the other machine. It binds to localhost by default. Leave it there.

When I need the panel from the laptop, I open a tunnel:

ssh -L 8385:127.0.0.1:8384 markhu@operations-box

Then visit port 8385 on the laptop. Same control, nothing open on the network, no password to forget. If you must expose it, set a strong panel password and TLS, though in my opinion that is one more server to babysit.

What cost me time, in one list

  1. The config lives in ~/.local/state/syncthing now. Old tutorials point to ~/.config/syncthing.
  2. Ping blocked means the machine dislikes ping. Test port 22000 directly.
  3. Automatic discovery works and dawdles. Set the local address yourself for home machines.
  4. UDP 22000 needs the same welcome as TCP. A hand-set address hides the missing rule.
  5. enable-linger or your headless box syncs nothing until someone logs in.
  6. Two processes with one service is normal. Read the parent-child line before you kill.

The setup itself took under an hour across both machines. Everything after hour one was firewalls and paths, which is the usual split with home infrastructure: the tool is the easy part, and the machine’s habits do the fighting. Both boxes are on the same pair today, twelve days after I started the service, and the folders I read this morning show no manual work in between. That is the whole point of a sync tool you forget about.


Corrections and topic requests: contact page. A real person reads every message.