Skip to main content

GUI Hardening

The Syncthing web GUI is a powerful API surface. By default it binds to 127.0.0.1:8384 with no password. On servers, leaving this unsecured is a critical risk.

Learning Focus

Lock down the GUI so it is accessible only through an SSH tunnel or a hardened reverse proxy, with a strong password and HTTPS enforced.

Tool Snapshot
PropertyDefaultHardened Target
Bind address127.0.0.1:8384127.0.0.1:8384 (keep localhost)
AuthenticationNoneUsername + bcrypt password
HTTPSDisabledEnabled (self-signed)
CSRF protectionEnabledEnabled — never disable
Remote accessDirect portSSH tunnel or nginx reverse proxy

Step 1 — Set a Username and Password

The safest method is through the GUI on first access. For scripted/headless servers, edit config.xml directly:

set-gui-password.sh
# Stop Syncthing before editing config
systemctl --user stop syncthing

# Generate a bcrypt hash of your password (Python 3)
python3 -c "import bcrypt; print(bcrypt.hashpw(b'yourStrongP@ss!', bcrypt.gensalt()).decode())"
# Output: $2b$12$...

# Edit config.xml
nano ~/.local/share/syncthing/config.xml

Find the <gui> block and add:

config.xml (gui section)
<gui enabled="true" tls="true">
<address>127.0.0.1:8384</address>
<user>admin</user>
<password>$2b$12$REPLACE_WITH_YOUR_BCRYPT_HASH</password>
<theme>default</theme>
</gui>
# Restart and verify
systemctl --user start syncthing
warning

Always use a bcrypt hash, never plaintext. Syncthing will silently accept plaintext but it is stored readable in config.xml.

Step 2 — Enable HTTPS on the GUI

config.xml — enable TLS on GUI
<gui enabled="true" tls="true">
<address>127.0.0.1:8384</address>
...
</gui>

Syncthing will generate a self-signed HTTPS certificate for the GUI. Your browser will show a certificate warning on first access — accept the exception once and it will not reappear.

Step 3 — Access via SSH Tunnel

On a remote VPS, never expose port 8384 to the internet:

ssh-tunnel-gui.sh
# Forward remote port 8384 to local port 8384
ssh -L 8384:127.0.0.1:8384 user@your-vps-ip -N &

# Now open in browser
# https://localhost:8384

For persistent access, add this to your ~/.ssh/config:

Host myvps-syncthing
HostName your-vps-ip
User ubuntu
LocalForward 8384 127.0.0.1:8384
ServerAliveInterval 60

Then simply run ssh myvps-syncthing -N and browse to https://localhost:8384.

Step 4 — Nginx Reverse Proxy (Optional)

For teams needing browser-based access without per-user SSH tunnels:

/etc/nginx/sites-available/syncthing
server {
listen 443 ssl;
server_name sync.internal.example.com;

ssl_certificate /etc/letsencrypt/live/sync.internal.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/sync.internal.example.com/privkey.pem;

# Only allow your office/VPN IP range
allow 10.0.0.0/8;
deny all;

location / {
proxy_pass http://127.0.0.1:8384;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# Required for Syncthing's CSRF validation
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 600s;
proxy_send_timeout 600s;
}
}
sudo nginx -t && sudo systemctl reload nginx
note

Syncthing's CSRF protection checks the Origin or Referer header. Always proxy with the correct Host header or the GUI will reject requests with a 403 CSRF Error.

GUI Hardening Checklist

ActionStatus Check
Password set (bcrypt)grep "<user>" ~/.local/share/syncthing/config.xml
TLS enabled on GUIgrep 'tls="true"' ~/.local/share/syncthing/config.xml
GUI bound to 127.0.0.1grep 127.0.0.1:8384 ~/.local/share/syncthing/config.xml
Port 8384 blocked externallysudo ufw status — 8384 should NOT be in ALLOW list
Access via SSH tunnel or proxyTest from external IP — should timeout or 403

Common Mistakes

MistakeRiskFix
No password on GUIAnyone on LAN can control SyncthingSet bcrypt password in <gui> block
Port 8384 open in firewallRemote attackers can access GUIsudo ufw deny 8384/tcp
Using HTTP (no TLS)Token and password sent in plaintextSet tls="true" in <gui> block
Disabling CSRF protectionXSS and CSRF attacks become possibleNever set insecureSkipHostcheck in production

What's Next