tursodatabase/turso · error · DatabaseError
overwriting the 'Host' header is not supported
Error message
overwriting the 'Host' header is not supported
What it means
DatabaseError thrown by the Session constructor when the session-level requestHeaders config contains a Host key, matched case-insensitively. Because connect()/new Session() is cheap and lazy (no I/O until the first query), this check runs eagerly at construction so the mistake surfaces before any request — fetch would otherwise silently drop the forbidden Host header on every call.
Source
Thrown at serverless/javascript/src/session.ts:99
* A database session that manages the connection state and baton.
*
* Each session maintains its own connection state and can execute SQL statements
* independently without interfering with other sessions.
*/
export class Session {
private config: SessionConfig;
private baton: string | null = null;
private baseUrl: string;
// Cached autocommit status from the server's last `get_autocommit` answer.
// A fresh connection is in autocommit (not in a transaction).
private autocommit: boolean = true;
constructor(config: SessionConfig) {
for (const name of Object.keys(config.requestHeaders ?? {})) {
// `Host` is a forbidden fetch header and would be silently dropped —
// reject it up front so the caller learns the override never takes effect.
if (name.toLowerCase() === 'host') {
throw new DatabaseError("overwriting the 'Host' header is not supported");
}
}
this.config = config;
this.baseUrl = normalizeUrl(config.url);
}
private httpContext(queryOptions?: QueryOptions): HttpContext {
// Per-query headers are merged over the session-level ones, so a query
// can override a header the session sets (and both override the
// standard headers).
let requestHeaders = this.config.requestHeaders;
if (queryOptions?.requestHeaders) {
requestHeaders = { ...requestHeaders, ...queryOptions.requestHeaders };
}
return {
url: this.baseUrl,
authToken: this.config.authToken,
remoteEncryptionKey: this.config.remoteEncryptionKey,View on GitHub (pinned to bad083fafb)
Solutions
- Remove Host from requestHeaders and change the connection url to route to the intended host
- When forwarding headers, whitelist only the keys you need (authorization, x-*) instead of spreading all of them
- Remember per-query QueryOptions.requestHeaders carries the same restriction — apply the same filtering there
Example fix
// before
const db = connect({ url, requestHeaders: { Host: "tenant-7.internal" } });
// after
const db = connect({ url: "https://tenant-7.internal", requestHeaders: { "x-tenant-id": "7" } }); Defensive patterns
Strategy: validation
Validate before calling
const stripHost = (h: Record<string, string> = {}): Record<string, string> =>
Object.fromEntries(
Object.entries(h).filter(([k]) => k.toLowerCase() !== "host"),
);
const db = connect({ url, authToken, requestHeaders: stripHost(forwardedHeaders) }); Prevention
- Route by changing url, never by setting Host — the constructor rejects it eagerly at connect() time
- Whitelist header keys when forwarding inbound headers into the client config
- Check both session-level requestHeaders and per-query QueryOptions.requestHeaders for 'host' in any casing
When it happens
Trigger: connect({ url, authToken, requestHeaders: { Host: 'tenant.example.com' } }); forwarding an inbound request's full header set (which includes host) into the client config; lowercase 'host' or uppercase 'HOST' keys, all rejected identically.
Common situations: Multi-tenant routing attempts via the Host header; shared header constants that bundle host with real overrides; copy-pasted proxy configuration blocks adapted into requestHeaders.
Related errors
- overwriting the 'Host' header is not supported
- retryFetch: attempts must be a finite integer >= 1, got ${at
- reader is null
- remoteWritesExperimental requires a non-null URL
- sync is disabled as database was opened without sync support
AI-assisted analysis of tursodatabase/turso@bad083fafb (2026-08-16).
Data as JSON: /api/errors/9e02b04f012b8c2a.
Report an issue: GitHub.