GUI unformatted using nginx proxy; CSS returns 404

I just started trying Syncthing on Ubuntu 22.04 LTS. Using version 1.27.8 installed via the Debian apt repository method.

I am using an nginx proxy as per the example in the docs, i.e. GUI accessible at mysite.com/syncthing

I am running the GUI at 127.0.0.1 and my proxy_pass points to that, and I set the Host header in the proxy to localhost.

However, the GUI is completely unformatted. All the HTML is there. Looking at the browser network tab, I see that all images and all CSS files, and the themes.json file all return a 404 response. The Javascript files load however.

I tried using an nginx rewrite rule “rewrite ^/syncthing(/.*)$ $1 break;” to fix the paths but that did nothing.

If I bind Syncthing to the WAN port and open up port 8384 for the GUI, the display works normally with all the nice formatting.

If I try using curl like “curl -H ‘Host: localhost’ http://127.0.0.1:8384/assets/css/theme.css” I do get the file. But proxying does not work.

So how do I get the GUI to work through an nginx proxy?

1 Like

One other observation I forgot. I notice the stylesheets reference in the HTML use an open path, like “assets/css/themes.css”. Why not make that a relative path, like “/assets/css/themes.css”, with the leading ‘/’?

On the last question, because the GUI might be served from under a subpath like you seem to do. As for why it doesn’t work, that’s hard to say. You’ll have to verify what path translation happens and make th eproxy do the right thing.

I am following the exact instructions on your own website here: https://docs.syncthing.net/users/reverseproxy.html

That does not work. I have guessed at various rewrite rules and sub_filters on NGINX but nothing works.

I notice within the HTML page, since the JavaScript files do work, they have a different path in the src attribute. The CSS files, which return 404, are referenced like this:

href=“assets/css/theme.css”

The Javascript files which do load for the gui are referenced like this:

src=“syncthing/core/module.js”

So you need to make your CSS links operate the same way as the GUI Javascript.

Or change the documentation to say that NGINX reverse proxies do not work for the GUI.

The GUI works behind a reverse proxy without any funky filters or rewrites.

Based on your error description I’m guessing that you’re missing a trailing slash in your proxy_pass directive, or that you’re using URL rewrites that mess up everything.

Note that when a path begins with a leading slash – e.g. “/assets/css/themes.css” – it’s known as an “absolute” path, while something like “assets/css/themes.css” is a “relative” path.

It appears that you’re confusing the “syncthing” path segment in “syncthing/core/module.js” with the subpath specified in the configurations for Nginx…

location /syncthing/ {
}

… and Apache HTTP Server:

<Location /syncthing/>
</Location>

Both “assets/css/theme.css” and “syncthing/core/module.js” are relative to your base URL.

So if your base URL is http://mysite.com/syncthing/, from the perspective of your web server, the full URLs to “assets/css/theme.css” and “syncthing/core/module.js” are as follows:

http://mysite.com/syncthing/assets/css/theme.css
http://mysite.com/syncthing/syncthing/core/module.js
1 Like

I understand that about proxy URL mapping. The bottom line is that the CSS files do not load, the JavaScript files do, and the paths I showed are in the HTML. I am not confusing anything, I am literally reporting what is in the HTML for the page of the GUI.

Sorry, that is not correct. I am using what was provided in the docs, with one update to allow non-localhosts to access this from https for mysite.com/syncthing/:

location /syncthing/ {
  proxy_set_header        Host localhost; # this was updated from $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;
}

Yeah I dunno man, I just tested now by spinning up an nginx container in Docker with that config and using it to proxy to my local Syncthing, and it loaded the GUI just fine, CSS and all.

    server {
        listen 0.0.0.0:8080;
        location /syncthing/ {
            proxy_set_header        Host localhost; # this was updated from $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://172.16.33.124:8384/;
            proxy_read_timeout      600s;
            proxy_send_timeout      600s;
        }
    }

As Gadget says, all the source links are relative, both CSS and Javascript. If they weren’t, this wouldn’t work.

1 Like

I am using dedicated servers running Ubuntu 22.04, and various sites are on these servers. Since NGINX logging doesn’t let me see what it proxies to, what can I hook into in your code to log what exactly is getting sent to the GUI process? That’s seems the only way for me to fix this.

STTRACE=api in the environment.

Unfortunately that does not work. I setup a log file at /var/log/syncthing.log. It looks like only the successful URLs are logged. I do not see any CSS file references at all after a day. Below is an example of what is logged, the working .js and .html files.

[LMANC] 2024/06/25 12:37:45.173484 api.go:531: DEBUG: http: GET "/syncthing/core/validDeviceidDirective.js": status 200, 1005 bytes in 0.26 ms
[LMANC] 2024/06/25 12:37:45.174735 api.go:531: DEBUG: http: GET "/syncthing/core/syncthingController.js": status 200, 150788 bytes in 1.97 ms
[LMANC] 2024/06/25 12:37:45.545979 api.go:531: DEBUG: http: GET "/syncthing/core/connectivityStatusModalView.html": status 200, 2657 bytes in 0.30 ms

A much simpler option is to temporarily shut down Syncthing and fire up a temporary web server. Ubuntu 22.04 LTS bundles Python 3, e.g.:

python -m http.server 8384

(By default, each web request is displayed in the console/terminal.)

Then run a few tests against your Nginx web proxy, e.g.:

curl http://mysite.com/syncthing/assets/css/theme.css

No, it works. If you’re not seeing failed requests, it’s because they’re not reaching Syncthing.

2024-06-26 07:35:20 http: GET "/assets/some/other/url": status 404, 19 bytes in 0.25 ms
2024-06-26 07:36:21 http: GET "/trololol/lol/lol": status 403, 10 bytes in 0.04 ms

I finally figured out the problem. On the site this proxy was under, I had a separate location match for .css files (e.g. “location ~* .css$ {”) to add a Cache-Control header for CDN and longer time frames of caching. The same was true for images and font files. That caused such files to be treated as 404 when called within the proxy URL space. Even though I had the proxy setup as per the docs, it allowed the other location match to run which cancelled the proxying because of adding the Cache-Control header.

To fix that I had to update your recommended NGINX proxy setting to this:

location ^~ /syncthing/ {

Notice the “^~” added to the location block, which tells NGINX to stop looking for more location matches. Then it worked. I already had the proxy configuration before the CSS / image file location match blocks, but I needed to prevent those from running inside the proxy URL space.

Therefore please update your docs to include that location setting and mention that the proxy location match should be first in the list of location settings for a website under NGINX.

This can be closed now.

1 Like