The Reader Question That Started This
A while back I wrote about running Syncthing over untrusted VPS relays, and someone in the comments asked a fair question: why not just use chezmoi for this instead? Here’s the short answer: don’t replace chezmoi with Syncthing, run both. chezmoi (or Stow, or a bare git repo) owns your templated, per-host, secret-bearing config: the files that need to render differently on your work laptop versus your home desktop, or that would leak a token if the repo ever got cloned somewhere it shouldn’t. Syncthing owns the big, boring, host-agnostic tool-state that nobody in their right mind puts in git: shell history, your Neovim data directory, ~/.ssh/known_hosts, tool caches.
They don’t compete, because they never touch the same files. The mistake, and the one this post is actually about, is treating Syncthing as a drop-in replacement for a dotfile manager. It isn’t one. It has no idea what a dotfile is, no per-machine templating, and no secrets story. Used for the wrong job, it will happily mirror your broken .vimrc to every machine you own within seconds. Used for the right job, keeping tool-state identical across machines you control, it’s genuinely the better tool. Here’s why, and then the setup that actually uses both.
What Syncthing Actually Does (and Doesn’t)
Syncthing has no idea what a dotfile is. It doesn’t know .bashrc is more important than a screenshot from three years ago. It’s a P2P, block-level, continuous file synchronization engine, nothing more. You point it at a folder on Machine A, pair it with Machine B, and from that point on, whatever bytes exist in that folder on one device get replicated to the other, in near-real-time, over an encrypted transport. No commits. No apply step. No “did I remember to push?” ritual. You save the file, and a few seconds later it exists elsewhere too.
That’s genuinely useful for a lot of use cases. But a mirror isn’t a dotfile management strategy. It reflects everything, including the ugly stuff.
Compare that to chezmoi, which treats your home directory as a build target. Your actual dotfiles live in a source-of-truth directory (usually ~/.local/share/chezmoi, backed by a git repo), and what ends up in ~/.bashrc is the rendered output of running templates against that source, with an explicit step to make it happen:
chezmoi init --apply git@github.com:yourname/dotfiles.gitOne command. Pulls the repo, renders every .tmpl file for this specific machine, and writes the result into your home directory. That per-machine rendering step is exactly what Syncthing cannot do: it has no concept of adjusting a file for a specific host, which is the core reason it can’t replace a dotfile manager.
If you want the full manager-vs-manager breakdown (chezmoi vs GNU Stow vs a bare git repo), I already did that fight in a separate post. This one isn’t about which manager wins. It’s about whether you should be using a manager at all, or just letting a sync daemon handle it.
The Case Against Syncthing as a Dotfile Tool
It propagates your mistakes just as fast as your good changes
Take this seriously: Syncthing doesn’t have opinions about whether a change is good. You save a half-edited .vimrc with a dangling bracket, or you rm your .ssh/config by accident while cleaning up, and Syncthing sees a changed file and does exactly what it’s designed to do: replicate it. Within seconds, your broken config (or your missing config) is now broken or missing on every other device sharing that folder too. There’s no “wait, are you sure?” There’s no review step. That’s not a bug, that’s the entire value proposition of a sync engine: continuous, unattended replication. And that same property is exactly what makes it dangerous for anything you haven’t reviewed yet.
Syncthing does have a safety net: file versioning. When you enable it on a shared folder, replaced or deleted files get moved into a hidden .stversions directory instead of being silently gone forever. You’ve got four strategies to choose from:
- Trash Can: keeps the previous version indefinitely (or until you clean it up manually)
- Simple: keeps the last N versions, first-in-first-out
- Staggered: keeps more recent versions densely and older ones sparsely, auto-expiring by age
- External: hands off to a script of your choosing when a file’s about to be replaced
Turn on Staggered for your synced folder and a busted .vimrc becomes recoverable: you can dig the last-good copy out of .stversions by hand. But notice what that is. A recycle bin, not source control. There’s no commit message, no diff, no branch, no record of why you changed something on March 3rd. Compare that to a dotfile manager backed by git, where git log -p -- .vimrc tells you exactly what changed and when, and git revert undoes it cleanly across every machine the next time you pull. .stversions gets you back last week’s file when you’ve already panicked. Git history gets you the reasoning behind the change. Those are not the same safety net.
No such thing as “this file, but different per machine”
The biggest structural problem is this: your work laptop and your personal desktop are not the same computer. Work wants a different git user.email. Your desktop has a different $PATH because you installed Homebrew in a different spot, or you’re not running the corporate VPN client wrapper in your shell profile. Your ultrawide monitor at home needs different DPI/scaling settings than the laptop’s built-in panel.
Syncthing has no concept of “sync this file, but adjust these three lines for this specific device.” A file is a file. Whatever bytes are on disk get mirrored, verbatim, to every other node in the share. Your options are: keep them byte-identical (and give up on legitimate per-host differences), maintain separate synced folders per host-type (which defeats half the point of syncing), or start excluding specific files from sync and manage those by hand anyway, which just means you’re doing manual dotfile management with extra plumbing.
chezmoi solves this natively with templating. A file like ~/.gitconfig becomes a source template dot_gitconfig.tmpl:
[user] name = KingPin email = {{ if eq .chezmoi.hostname "work-laptop" }}kingpin@corp.example.com{{ else }}me@personal.example.com{{ end }}
[core] pager = {{ if eq .chezmoi.os "darwin" }}delta{{ else }}less{{ end }}chezmoi apply renders that template against machine-specific data (.chezmoi.hostname, .chezmoi.os, or values you define yourself in .chezmoi.toml.tmpl) and writes out the correct ~/.gitconfig for that machine. One source file, N correct outputs. This is the single biggest reason Syncthing struggles as a pure dotfile tool. Per-host rendering isn’t a missing feature you could bolt on later. Mirroring and rendering are fundamentally different models.
Secrets ride along in the clear
chezmoi’s whole secrets story is about keeping sensitive values out of the source repo entirely. You template them in from an external secret store (age-encrypted files, 1Password, Bitwarden, HashiCorp Vault) and chezmoi pulls them in at apply-time:
machine api.example.comlogin {{ .netrc_user }}password {{ (bitwarden "item" "api.example.com").login.password }}The repo itself never contains the plaintext password. That matters a lot if your dotfiles repo lives on GitHub, or you ever apply it on a machine you don’t fully trust.
Syncthing has no redaction layer, because it doesn’t know what a “secret” is any more than it knows what a dotfile is. Whatever plaintext exists in the synced folder (your .netrc, your API tokens in a shell profile, an SSH key you foolishly keep in ~/dotfiles/.ssh/id_ed25519) gets synced as-is, in full, to every paired device. In fairness, this isn’t a security hole in the traditional sense: Syncthing’s transport is end-to-end encrypted and P2P, and no third-party server ever sees the plaintext. But “the pipe is secure” and “the secret is redacted from the source of truth” are different guarantees. With Syncthing, the secret exists in full, unredacted form, on every single node you’ve paired. Lose control of any one of them and it’s gone.
Bootstrapping a fresh machine takes more steps, not fewer
With a dotfile manager, day one on a new machine is one command (chezmoi init --apply <repo>) and you’re done: install the binary, run the command, walk away with a fully configured environment. Syncthing’s bootstrap looks different: install the Syncthing binary/package, start the daemon, open the web GUI, generate this device’s ID, go to the other device and manually add the new device by pasting its ID, accept the pairing request on both ends, then share the specific folder from the existing device to the new one, and wait for the initial full sync to finish before your .bashrc actually exists. It works, but it’s more clicks, both machines need to be online at the same time at some point, and there’s no equivalent of “apply a template for this specific host” baked into the flow, because Syncthing doesn’t do templates.
Where Syncthing Actually Wins
None of the above means Syncthing is bad. It means Syncthing is solving a different problem than “manage my dotfiles well.” Here’s where it earns its keep:
It’s continuous and frictionless. No commit, no push, no apply. You edit a file, it’s on the other machine a few seconds later. No “oh no, I forgot to sync before I left the house” moment ever again.
It syncs stuff dotfile managers have no business touching. Your Neovim plugin directory (~/.local/share/nvim), shell history, ~/.ssh/known_hosts, whole tool-state directories, even browser profiles or large binary blobs. Nobody wants to chezmoi add a gigabyte of plugin cache into a git repo. Syncthing doesn’t care that it’s not “config” in the traditional sense. It just replicates the directory.
A word of caution on that tool-state claim, because not all of it is safe to sync blindly. Compiled Neovim plugin binaries and other architecture-specific artifacts can break the moment they land on a machine with a different CPU architecture. Some caches genuinely corrupt if two machines write to them around the same time. Shell history files can turn into a merge-conflict mess when two sessions write simultaneously. The fix is to be selective about which tool-state you sync: durable, architecture-independent stuff (known_hosts, plugin manifests, your actual snippets and settings) is safe to mirror everywhere. Anything regenerable, or tied to a specific machine’s architecture, belongs in .stignore instead.
No third party required, at all, if you don’t want one. No GitHub, no git server, nothing. Pure P2P between devices you own. If your threat model is “I don’t want my dotfiles anywhere but my own hardware,” Syncthing gets you there without a hosting decision.
It’s fantastic for the identical-environment case. Two personal Linux boxes you want to behave like clones of each other: same shell history, same tool state, same everything, no per-host differences to reconcile. That’s exactly the scenario Syncthing was built for. The “no per-host config” weakness above stops being a weakness the moment there genuinely isn’t supposed to be a per-host difference.
The Setup That Actually Makes Sense: Both
The verdict is not “pick a side.” Syncthing is the wrong tool if your actual problem is managing dotfiles well across machines that need to differ from each other. For that, use chezmoi, or Stow, or a bare repo. Syncthing is the right tool when your actual problem is keeping your whole environment and tool-state in lockstep across machines you fully control, including stuff that has zero business being tracked in git.
The mature setup uses both, and they don’t step on each other because they’re not touching the same files:
// Managed by chezmoi: don't let Syncthing anywhere near these.// chezmoi apply is the only thing allowed to write them..bashrc.zshrc.gitconfig.vimrc.config/starship.tomlAnything you don’t list in .stignore syncs by default, and that’s the point. With just those five lines excluded, Syncthing quietly owns everything else in the folder: the big, boring, host-agnostic tool-state chezmoi has no opinion about, like ~/.local/share/nvim, ~/.local/share/zsh, ~/.ssh/known_hosts, or ~/.cache/gh. (.stignore is a deny-list. An exclamation-mark ! prefix only matters as an exception carved out of a broader ignore rule above it, so there’s no need for one here.) chezmoi owns the templated, per-host, secret-bearing config: the stuff that genuinely needs to differ between your work laptop and your home desktop, or that shouldn’t leak a plaintext token if the repo ever gets cloned somewhere it shouldn’t. Syncthing owns the big, boring, host-agnostic state that just needs to be the same everywhere and that no dotfile manager would sanely track anyway.
Neither tool is wrong here. The mistake is using Syncthing as a full replacement for a dotfile manager. Syncthing is good software; it’s just solving “keep bytes identical,” while dotfiles across heterogeneous machines are fundamentally a “render the right bytes for this specific machine” problem. Use the right tool for each half, and you stop fighting both of them.