Load doc content error" on every document, including brand new ones - 404 on /sdoc-server/api/v1/docs/{uuid}/

SeaDoc: “Load doc content error” on every document, including brand new ones - 404 on /sdoc-server/api/v1/docs/{uuid}/

Environment

  • Seafile Community Edition 13.0.25 (Docker, official seafile-server.yml + seadoc.yml, no Caddy — using an external HAProxy reverse proxy instead)

  • SeaDoc image: seafileltd/sdoc-server:2.0-latest (reports server_version=2.0.9)

  • MariaDB 10.11, Redis (no password), single Docker host, seafile-net bridge network

  • Metadata server (seafile-md-server:13.0-latest) also deployed and confirmed stable/healthy

  • Reverse proxy: HAProxy (pfSense package), not the bundled Caddy. All internal ports (8000 for Seafile, 8888 for SeaDoc) are proxied correctly.

Symptom

Opening any .sdoc file (including a file created seconds earlier, on a completely fresh test) shows “Load doc content error” in the browser. Console shows:

XHR GET https://<domain>/sdoc-server/api/v1/docs/<uuid>/
[HTTP/2 404  ~1-2ms]

The response time is consistently 1-2ms, for every single request, every single document UUID, with no exceptions.

What has been verified working

  1. HAProxy routing — confirmed correct with curl, matching exactly what the browser sends:

    curl -I https://<domain>/sdoc-server/api/v1/docs/<uuid>/
    → HTTP/2 403 (Express, correct — no auth token in this manual test)
    
    

    The 403 (not 404) on an unauthenticated manual request confirms SeaDoc itself is reachable and responds differently based on auth state.

  2. seahub_settings.py — contains (added manually, since these were NOT auto-populated despite ENABLE_SEADOC=true in .env and seadoc.yml in COMPOSE_FILE):

    ENABLE_SEADOC = True
    SEADOC_SERVER_URL = 'https://<domain>/sdoc-server'
    SEADOC_PRIVATE_KEY = '<same value as JWT_PRIVATE_KEY>'
    FILE_CONVERTER_SERVER_URL = 'http://seadoc:80'
    
    

    (Also tried SEADOC_SERVER_URL = 'http://seadoc:80' — no difference.)

  3. JWT_PRIVATE_KEY matches exactly between .env (used by seadoc.yml) and SEADOC_PRIVATE_KEY in seahub_settings.py — verified byte-for-byte via printenv inside the seadoc container vs. grep in seahub_settings.py.

  4. System clocks — verified identical (UTC, to the second) across host, seafile container, and seadoc container. JWT exp claim is far in the future, not expired.

  5. Docker networking — both containers on the same seafile-net bridge network. docker exec -it seadoc curl -I http://seafile returns a normal 302 redirect to /accounts/login/, confirming SeaDoc can reach Seafile over the internal network.

  6. The JWT token itself (decoded via jwt.io) contains a well-formed payload:

    {
      "file_uuid": "<matches the uuid in the failing request>",
      "filename": "ppp.sdoc",
      "username": "user@domain.tld",
      "name": "...",
      "avatar_url": "...",
      "permission": "rw",
      "default_title": "ppp.sdoc",
      "exp": 1786039348
    }
    
    
  7. No POST/create request ever appears in SeaDoc’s own sdoc-access.log, only failing GET requests — meaning Seahub never seems to call SeaDoc to initialize/register a new document; only the browser’s GET to fetch content is ever seen, and it always 404s.

  8. SeaDoc’s own database tables (sdoc_comment_reply, sdoc_draft, sdoc_notification, sdoc_operation_log, sdoc_revision) exist in seahub_db, but there is no obvious “documents” table — presumably content is meant to be fetched live from Seafile via SEAHUB_SERVICE_URL (currently default http://seafile), not stored in SeaDoc’s DB directly.

  9. Removed the stale Caddy container entirely (was left over from initial deployment, unused since switching to HAProxy) — no change in behavior.

  10. Cleared the __pycache__/seahub_settings.cpython-312.pyc file and did full container restarts after every settings change, to rule out stale bytecode caching.

Question

Given that:

  • routing/auth/network/clocks are all confirmed correct,

  • the 404 response time is suspiciously fast (1-2ms) for what should involve an internal callback to Seafile to fetch file content,

  • no create/init request from Seahub to SeaDoc is ever logged, even for a file created seconds before opening,

what is the expected request flow between Seahub and SeaDoc when a new .sdoc file is opened for the first time? Is there a specific API call Seahub is supposed to make to SeaDoc at file-creation time (or first-open time) that might be silently failing or simply not implemented/triggered in this setup? Any pointers on additional logging to enable, or a compatibility requirement between sdoc-server:2.0.9 and Seafile CE 13.0.25 that we might be missing, would be greatly appreciated.

Happy to provide any further logs/config on request.

Based on your detailed troubleshooting, the key issue is likely the 302 redirect you observed when running curl -I http://seafile from within the SeaDoc container.

When the browser requests a document from SeaDoc, the SeaDoc server (sdoc-server) must internally fetch the file content from the Seafile server. If Seafile responds with a 302 redirect to a login page instead of the file content or an API response, SeaDoc fails to initialize the document and returns a 404 to the browser.

Here are the recommended steps to resolve this:

1. Check sdoc-server.log

Inspect the internal log inside the SeaDoc container (usually at /shared/logs/sdoc-server.log). You will likely see errors such as Get doc download link error or failed to fetch document. This will confirm if the communication between SeaDoc and Seafile is failing.

2. Verify ALLOWED_HOSTS

In your seahub_settings.py, ensure that the internal hostname seafile is included in the ALLOWED_HOSTS setting if you have ALLOWED_HOSTS setting (or you can delete the setting):

ALLOWED_HOSTS = ['<your-domain>', 'seafile', '127.0.0.1']

If seafile (the internal Docker service name) is missing, Seahub may reject or redirect internal requests from the SeaDoc container.

3. Check SEAHUB_SERVICE_URL in SeaDoc

The SeaDoc container uses the environment variable SEAHUB_SERVICE_URL to know how to reach Seafile.

  • In your seadoc.yml, check if SEAHUB_SERVICE_URL is set correctly (default is http://seafile).
  • If you are using a non-standard port or if Seafile is listening on a specific internal address, ensure this URL matches.
  • Since you are using an external proxy, ensure that internal traffic (container-to-container) does not try to go out to the proxy and back in if it can be handled locally.

4. CSRF and Trusted Origins

Since Seafile 11.0+, CSRF protections are stricter. Ensure your CSRF_TRUSTED_ORIGINS includes your external domain:

CSRF_TRUSTED_ORIGINS = ['https://<your-domain>']

Why ENABLE_SEADOC=true didn’t work

The fact that ENABLE_SEADOC=true in .env didn’t auto-populate seahub_settings.py suggests that the initial configuration script didn’t run or didn’t have write permissions. Since you have already added the settings manually, the manual entries are correct, but ensure SEADOC_PRIVATE_KEY and JWT_PRIVATE_KEY are identical.

Once the curl -I http://seafile command from the SeaDoc container returns a successful response (or at least not a 302 redirect to /accounts/login/), the “Load doc content error” should be resolved.

Thanks @daniel.pan, really appreciate the detailed reply!

I went through each point:

  1. CSRF_TRUSTED_ORIGINS — added CSRF_TRUSTED_ORIGINS = ['<your-domain, no protocol shown here>'] to seahub_settings.py, cleared the __pycache__, restarted. No change — still an immediate 404 on every document, including brand new ones created after this change.

  2. ALLOWED_HOSTS — already tried this earlier (added seafile alongside the domain), no change either.

  3. SEADOC_PRIVATE_KEY vs JWT_PRIVATE_KEY — confirmed byte-for-byte identical (compared via printenv inside the seadoc container vs. grep in seahub_settings.py).

  4. curl -I http://seafile from inside the SeaDoc container — this does return a 302 to /accounts/login/, but I want to flag that this seems to just be normal Django behavior for any unauthenticated request to / — it’s not necessarily proof that SeaDoc’s actual internal request (whatever specific endpoint/headers it uses) is failing the same way. A generic root request isn’t really equivalent to what SeaDoc’s Go backend would call internally.

  5. Checked sdoc-server.log specifically for the error strings you mentioned (Get doc download link error, failed to fetch document) — neither ever appears, not once, in any of the logs (searched the full log history, not just around recent test timestamps). The only entries in sdoc-server.log are periodic “0 docs saved” autosave-timer messages and the access log lines (which just show the 404 responses themselves, in ~1-2ms each — too fast to represent a real outbound callback attempt to Seafile).

  6. Also confirmed: no WebSocket / socket io connection is ever established in the browser when opening a document (checked both the “WS” filter and a broader socket io filter in DevTools Network tab — nothing shows up at all, not even a failed polling attempt).

Given that:

  • there’s no trace of SeaDoc ever attempting to reach Seahub (no error logged, no matching log entry at all),

  • and no socket io handshake is ever attempted from the browser,

could you tell me the exact internal API endpoint (and method/headers) that sdoc-server is supposed to call on Seahub to fetch/initialize a document’s content? I’d like to test that specific endpoint directly with curl (with a valid JWT) to see whether it’s Seahub or SeaDoc that’s failing to even attempt the call in the first place.

Happy to share the full sdoc-server.log / seahub.log output around a fresh test if that helps narrow it down further.

If there is no log about sdoc-server to fetch content from seafile-server (to seahub component), can you first open the browser console to check which URL return 404 to the frontend? Is 404 really returned by sdoc-server?

Yes, confirmed — the 404 is genuinely returned by sdoc-server itself, not by Seafile/Seahub’s nginx.

Evidence:

  1. The exact same URL and UUID appear in sdoc-server’s own sdoc-access.log (inside the seadoc container):
[INFO] requestTime - GET /sdoc-server/api/v1/docs/<uuid>/ 404 1.2ms

  1. The browser console shows the identical request/UUID:
XHR GET https://<your-domain>/sdoc-server/api/v1/docs/<uuid>/
[HTTP/2 404]

  1. The response headers include X-Powered-By: Express (sdoc-server’s Node.js stack), not Django/nginx — so this isn’t a generic Seafile-side 404 being returned instead.

I also tested manually with curl (no auth token) against the same path and got a 403 from sdoc-server (also with the Express header) — so sdoc-server is definitely handling the request and returning its own responses based on auth state; it’s not falling through to some other service returning a default 404.

So to summarize: the request correctly reaches sdoc-server, sdoc-server responds with 404, but there is no error logged anywhere in sdoc-server’s own logs explaining why — as if it just doesn’t recognize/find the document at all, instantly, without attempting anything (like a callback to Seahub) that would take longer than ~1-2ms.

There is an error in your proxy URL rewrite rule:

In sdoc-server, it should see “/api/v1/docs//” instead of " /sdoc-server/api/v1/docs// "

You should remove “/sdoc-server/” part.

Found it — turned out to be two separate issues stacked on top of each other. Posting the full solution here in case it helps someone else running a custom reverse proxy (HAProxy in my case) instead of the bundled Caddy setup.

Issue 1: Missing path rewrite on the reverse proxy

My reverse proxy was forwarding the full path /sdoc-server/api/v1/docs/<uuid>/ straight through to sdoc-server. But sdoc-server expects to receive /api/v1/docs/<uuid>/ — the /sdoc-server/ prefix needs to be stripped before the request reaches the sdoc-server container. This is handled automatically by Caddy (and would be handled by Seafile’s internal nginx too, if using that path), but since I’m proxying directly to the sdoc-server container, I had to add this manually.

Fix (HAProxy syntax, but the equivalent applies to any reverse proxy — just strip the /sdoc-server/ prefix):

http-request set-path %[path,regsub(^/sdoc-server/,/)]

After this fix, the response changed from an instant (~1-2ms) 404 to a slower (~87ms) 500 — meaning sdoc-server was now actually receiving valid requests and attempting to process them, instead of failing to even recognize the path.

Issue 2: DNS resolution failing inside the sdoc-server container

With the path fix in place, sdoc-server.log finally showed a real, specific error:

[ERROR] error_message:  getaddrinfo EAI_AGAIN <my-domain>
[ERROR] Load <file>.sdoc(<uuid>) from https://<my-domain>/seafhttp/files/<...>/<file>.sdoc error

sdoc-server fetches file content from Seahub via the public domain URL (SEAHUB_SERVICE_URL resolves to this in the actual outbound request, not just the internal container name), and DNS resolution for that public domain was failing from inside the Docker containers themselves (getent hosts <my-domain> returned nothing from both the seafile and seadoc containers).

Fix: explicitly set a working DNS server for the Docker daemon (I pointed it at my own router/firewall’s resolver, since it already correctly resolves the public domain):

/etc/docker/daemon.json:

{
  "dns": ["<your-internal-DNS-resolver-IP>"]
}
sudo systemctl restart docker

After both fixes, documents open and save correctly.

Thanks again @daniel.pan for the guidance along the way — the “is it really sdoc-server returning the 404” question was what pointed me toward digging into the actual path being received, which led to fix #1.