How to properly set up Syncthing GUI with Nginx?

Hi all! Basically the title: I am configuring a NixOS home lab, and I have all the services behind Nginx. The server is also running Headscale, and the connections to the services (except Headscale) are only accepted from the tailnet. This works for other services, but I really have troubles in making the Syncthing GUI work in this way. What I ended up with is this configuration, that works:

{
  services.syncthing = {
    enable = true;
    openDefaultPorts = true;

    # Accept from everyone, not just localhost
    guiAddress = "0.0.0.0:8384";
    # To avoid hard-coding it or setting it via GUI
    guiPasswordFile = "/var/secrets/syncthing-admin-pass";
    settings.gui = {
      user = "syncthing";
    };
  };

  # Only accept connections from the VPN
  networking.firewall.interfaces."tailscale0".allowedTCPPorts = [ 8384 ];

  services.nginx.virtualHosts."syncthing.my-domain.net" = {

    useACMEHost = "my-domain.net"; # Use my wildcard SSL certificate
    forceSSL = true;

    locations."/" = {
      proxyPass = "http://127.0.0.1:8384";
      proxyWebsockets = true;

      extraConfig = ''
        allow 100.64.0.0/10;
        allow 127.0.0.1;
        deny all;

        client_max_body_size 50000M;
        proxy_read_timeout 600s;
        proxy_send_timeout 600s;
        send_timeout 600s;
      '';
    };
  };

  services.headscale.settings.dns.extra_records = [
    { name = "syncthing.${pubDomain}";  type = "A"; value = "100.64.0.1"; } # The tailnet IP of the NixOS server
  ];

}

But I have some questions:

  1. If I change
guiAddress = "0.0.0.0:8384";
# to =>
guiAddress = "127.0.0.1:8384";

I get

HTTP/2 403 - Host check error
  1. In the Syncthing wiki about using Nginx, it’s recommended to use these options:
location /syncthing/ {
  proxy_set_header        Host $host;
  proxy_set_header        X-Real-IP $remote_addr;
  proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
  proxy_set_header        X-Forwarded-Proto $scheme;

  proxy_pass              http://localhost:8384/;

  proxy_read_timeout      600s;
  proxy_send_timeout      600s;
}

But if use them as the extraConfig of the Nginx virtualHost (instead of the one I used), I get

HTTP/2 400 - Bad Request

What’s going on? Shouldn’t the “usual” reverse proxying work (as in scheme)? And why do I get these errors? Are the recommended Nginx options wrong? Thanks!


                                                          +----------------------------------------------------+
                                                          |    +----------------+         +---------------+    |
      +---------------+                                   |    |                |         |               |    |
      |               |       +----------------------+HTTPS on |                | localhost               |    |
      |   Client      |------>|    Tailscale VPN     |----+--->|  Nginx         |-------->|  Syncthing    |    |
      |               |       +----------------------+subdomain|                | connection              |    |
      +---------------+                                   |    |                | port 8384               |    |
                                                          |    |                |         |               |    |
                                                          |    |                |         |               |    |
                                                          |    +----------------+         +---------------+    |
                                                          |                                                    |
                                                          |             NixOS home lab                         |
                                                          |                                                    |
                                                          |                                                    |
                                                          +----------------------------------------------------+

This is because the guiAdress = "127.0.0.1:8384" will make the GUI socket accept connections only from 127.0.0.1 aka localhost.

This is likely syncthings host header check kicking in. Quoting from the FAQ:

Why do I get “Host checkerror” in the GUI/API?

Since version 0.14.6 Syncthing does an extra security check when the GUI/API is bound to localhost - namely that the browser is talking to localhost. This protects against most forms of DNS rebinding attack against the GUI.

To pass this test, ensure that you are accessing the GUI using an URL that begins with http://localhost, http://127.0.0.1 or http://[::1]. HTTPS is fine too, of course.

If you are using a proxy in front of Syncthing you may need to disable this check, after ensuring that the proxy provides sufficient authentication to protect against unauthorized access. Either:

  • Make sure the proxy sets a Host header containing localhost, or

  • Set gui.insecureSkipHostcheck in the advanced settings, or

  • Bind the GUI/API to a non-localhostlisten port.

In all cases, username/password authentication and HTTPS should be used.