actualbudget/actual · error
Unable to resolve host: ${hostname}
Error message
Unable to resolve host: ${hostname} What it means
When the URL host is not a literal IP, assertUrlAllowed resolves it via DNS (dnsLookup with { all: true }). If the lookup fails (NXDOMAIN, resolver outage, no network), it throws 'Unable to resolve host' naming the hostname, so the server never attempts the outbound request.
Source
Thrown at packages/sync-server/src/util/ssrf.ts:104
// URL keeps the surrounding brackets on IPv6 hosts (e.g. "[::1]"); strip
// them so the address can be parsed and resolved.
const hostname = url.hostname.replace(/^\[|\]$/g, '');
// Literal IP address: check it directly without a DNS lookup.
if (ipaddr.isValid(hostname)) {
if (isBlockedIp(hostname, options)) {
throw new Error(`Blocked request to private/local IP: ${hostname}`);
}
return;
}
// Hostname: resolve every address it points to and reject if any is blocked.
let addresses: { address: string }[];
try {
addresses = await dnsLookup(hostname, { all: true });
} catch {
throw new Error(`Unable to resolve host: ${hostname}`);
}
if (addresses.length === 0) {
throw new Error(`Unable to resolve host: ${hostname}`);
}
for (const { address } of addresses) {
if (isBlockedIp(address, options)) {
throw new Error(
`Blocked request to host resolving to private/local IP: ${hostname} (${address})`,
);
}
}
}
View on GitHub (pinned to d4334cb6e6)
Solutions
- Verify the hostname is spelled correctly and exists in DNS (`nslookup <host>` from the server host).
- Register the internal hostname in a resolver the server can query, or use an IP-backed public name.
- Check the server's DNS configuration (/etc/resolv.conf, container DNS settings).
- Confirm the server host has outbound network access.
Example fix
// before const url = 'https://sync.mydomain.con'; // typo // after const url = 'https://sync.mydomain.com'; // resolvable hostname
Defensive patterns
Strategy: retry
Validate before calling
import { lookup } from 'dns/promises';
async function hostResolves(hostname) {
try {
return (await lookup(hostname, { all: true })).length > 0;
} catch {
return false;
}
} Try / catch
try {
await assertUrlAllowed(targetUrl);
} catch (e) {
if (e.message.startsWith('Unable to resolve host')) {
// transient resolver failures are common — retry with backoff
await sleep(1000);
return retryAssert(targetUrl, 3);
}
throw e;
} Prevention
- Verify hostnames in DNS before configuring them (`dig <host> +short`).
- Ensure the server host/container has working DNS resolvers.
- Prefer stable public DNS records for endpoints.
- Retry DNS-dependent checks a few times before failing.
When it happens
Trigger: Calling claimAccessKey/getAccounts with a hostname that does not exist in DNS, a typo'd domain, a DNS resolver outage, or an internal-only hostname (e.g. 'sync.internal') that the server's resolver cannot answer.
Common situations: Typo in the bootstrap/server URL (e.g. '.con' instead of '.com'); self-hosted DNS names only resolvable on the LAN while the server runs elsewhere; DNS outage at the hosting provider; container without DNS configured.
Related errors
- Blocked request to private/local IP: ${hostname}
- Blocked request to host resolving to private/local IP: ${hos
- No sync server configured.
- getServerErrorReason(json)
- network-failure
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/e441e6bec0aaeb98.
Report an issue: GitHub.