fatedier/frp · error · HTTPError

HTTP ${response.status}

Error message

HTTP ${response.status}

What it means

Generic non-2xx failure from the frps dashboard's plain fetch wrapper (request()). Any frps API call that receives a non-OK HTTP status throws an HTTPError with message 'HTTP <status>'. The frps dashboard API sits behind optional basic auth (dashboard.user/dashboard.password) and requires the dashboard port, not the bind port.

Source

Thrown at web/frps/src/api/http.ts:37

export interface V2Page<T> {
  total: number
  page: number
  pageSize: number
  items: T[]
}

type QueryParamValue = string | number | boolean | null | undefined

async function request<T>(url: string, options: RequestInit = {}): Promise<T> {
  const defaultOptions: RequestInit = {
    credentials: 'include',
  }

  const response = await fetch(url, { ...defaultOptions, ...options })

  if (!response.ok) {
    throw new HTTPError(
      response.status,
      response.statusText,
      `HTTP ${response.status}`,
    )
  }

  // Handle empty response (e.g. 204 No Content)
  if (response.status === 204) {
    return {} as T
  }

  return response.json()
}

async function requestV2<T>(
  url: string,
  options: RequestInit = {},
): Promise<T> {

View on GitHub (pinned to 6c8a8d0a97)

Solutions

  1. Confirm the URL uses frps webServer.port (dashboard port), not the proxy bindPort
  2. Provide dashboard basic-auth credentials (the wrapper relies on credentials:'include' cookies from the login flow)
  3. Check err.status in the catch: 401 → auth, 404 → wrong path or old frps, 5xx → server logs
  4. Upgrade frps so its API surface matches the dashboard's expected endpoints

Example fix

// before
const info = await http.get('/api/serverinfo')

// after
const info = await http.get('/api/serverinfo', {
  headers: { Authorization: 'Basic ' + btoa(`${user}:${pass}`) },
})
Defensive patterns

Strategy: try-catch

Validate before calling

// Confirm the frps dashboard port responds before the first API call
const probe = await fetch(`${base}/api/serverinfo`, { credentials: 'include' })
if (probe.status === 401) await login() // establish basic-auth/session first

Type guard

function isHTTPError(e: unknown): e is { status: number } & Error {
  return e instanceof Error && typeof (e as any).status === 'number'
}

Try / catch

try {
  return await http.get<T>('/api/serverinfo')
} catch (e) {
  if (isHTTPError(e)) {
    switch (e.status) {
      case 401: return handleAuthExpired()
      case 404: return handleWrongEndpoint(e)
    }
  }
  throw e
}

Prevention

When it happens

Trigger: Calling frps API endpoints (/api/serverinfo, /api/proxy/tcp, etc.) when the response status is not 2xx: 401 when dashboard basic-auth credentials are wrong or credentials:'include' cookies are missing, 404 for an endpoint that does not exist on that frps version, 500 on server-side handler errors.

Common situations: Accessing the API port instead of the dashboard port (bindPort vs webServer.port); dashboard.password set but the request sent no credentials; older frps version that lacks newer API routes; reverse proxy stripping Authorization headers.

Related errors


AI-assisted analysis of fatedier/frp@6c8a8d0a97 (2026-08-15). Data as JSON: /api/errors/391f81fe54a6df2d. Report an issue: GitHub.