{"record":{"id":"d36b3a45cc21d8e9","repo":"gethomepage/homepage","slug":"unable-to-login-to-duplicati-status-status","errorCode":null,"errorMessage":"Unable to login to Duplicati (status ${status})","messagePattern":"Unable to login to Duplicati \\(status (.+?)\\)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/widgets/duplicati/proxy.js","lineNumber":62,"sourceCode":"    nextRun: nextRunTime?.toUTC().toISO() ?? null,\n  };\n}\n\nasync function login(widget) {\n  const loginUrl = new URL(formatApiCall(widgets[widget.type].api, { endpoint: \"auth/login\", ...widget }));\n  const [status, , data] = await httpProxy(loginUrl, {\n    method: \"POST\",\n    headers: {\n      \"Content-Type\": \"application/json\",\n    },\n    body: JSON.stringify({\n      Password: String(widget.password),\n      RememberMe: true,\n    }),\n  });\n\n  if (status !== 200) {\n    throw new Error(`Unable to login to Duplicati (status ${status})`);\n  }\n\n  const body = asJson(data);\n  if (!body?.AccessToken) {\n    throw new Error(\"Duplicati login response did not include an access token\");\n  }\n\n  return body.AccessToken;\n}\n\nasync function apiGet(widget, endpoint, accessToken) {\n  const url = new URL(formatApiCall(widgets[widget.type].api, { endpoint, ...widget }));\n  const [status, , data] = await httpProxy(url, {\n    method: \"GET\",\n    headers: {\n      Authorization: `Bearer ${accessToken}`,\n    },\n  });","sourceCodeStart":44,"sourceCodeEnd":80,"githubUrl":"https://github.com/gethomepage/homepage/blob/b6dca1ae033e613d8e692f9a161a3cc53a5a2857/src/widgets/duplicati/proxy.js#L44-L80","documentation":"Thrown by the Duplicati proxy's login step when the POST to the login endpoint returns any status other than 200. Duplicati requires a login that exchanges the configured password for an AccessToken before any data request, so a failed login aborts the whole proxy call chain.","triggerScenarios":"Wrong password configured for the Duplicati widget (login returns 401/422), Duplicati server unreachable or returning 500, login URL malformed so it 404s, or Duplicati restart/capacity lock returning 503 during the window the widget polls.","commonSituations":"Password copied with surrounding whitespace or quotes; Duplicati password changed but widget config not updated; base URL wrong (path/port/scheme); Duplicati version change that altered the login endpoint; running behind a reverse proxy that strips the POST body or sets the wrong Content-Type.","solutions":["Re-enter the Duplicati password in the Homepage widget config exactly as set in Duplicati, trimming any whitespace.","Confirm the Duplicati base URL and port are correct and reachable from the Homepage container.","Log in to Duplicati's web UI with the same password to confirm the credential itself works.","Check the reverse proxy passes the JSON POST body and Content-Type: application/json through unchanged.","Raise Homepage debug logging to capture the status code that precedes the throw, then map it (401 vs 500 vs 502) to the right cause."],"exampleFix":"// before\nif (status !== 200) {\n  throw new Error(`Unable to login to Duplicati (status ${status})`);\n}\n// after\nif (status !== 200) {\n  throw new Error(`Duplicati login at ${loginUrl} failed (HTTP ${status}): ${Buffer.from(data).toString().slice(0, 200)}`);\n}","handlingStrategy":"try-catch","validationCode":"// Sanity-check the Duplicati config before invoking login.\nfunction validateDuplicatiWidget(w) {\n  if (!w?.url || !/^https?:\\/\\//.test(w.url)) throw new Error(\"Duplicati url invalid\");\n  if (typeof w.password !== \"string\" || w.password.length === 0) throw new Error(\"Duplicati password missing\");\n  return true;\n}","typeGuard":"function hasDuplicatiCredentials(widget) {\n  return Boolean(widget && typeof widget.url === \"string\" && typeof widget.password === \"string\" && widget.password.length > 0);\n}","tryCatchPattern":"try {\n  const token = await login(widget);\n  return await apiGet(widget, endpoint, token);\n} catch (err) {\n  if (/Unable to login/.test(err.message)) {\n    res.status(401).json({ error: \"Duplicati login failed\", hint: \"check password/URL\" });\n  } else {\n    throw err;\n  }\n}","preventionTips":["Test the Duplicati password by logging into the web UI before configuring the widget.","Keep the Duplicati URL and password in env-backed config so they survive container rebuilds.","Log the failing status separately so you can distinguish 401 (credential) from 502 (proxy)."],"tags":["network","duplicati","authentication","http-proxy","widget"],"backgroundTag":null,"analyzedSha":"b6dca1ae033e613d8e692f9a161a3cc53a5a2857","analyzedAt":"2026-08-13T04:48:44.121Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}