Invalid Host Header error


Your local app trusts only localhost in development, so it refuses the hostname which LocalCan forwards.

The wording depends on the framework, and they all mean the same thing:

  • "Blocked request. This host is not allowed" comes from Vite, so also from SvelteKit, Nuxt, Astro, and Angular.
  • "Blocked cross-origin request to Next.js dev resource" comes from Next.js.
  • "Blocked hosts" is the Rails error page.
  • "Invalid HTTP_HOST header" is Django.
  • "Invalid Host header" is a webpack dev server, including Create React App and Vue CLI.

A bare 403 with an empty page is the same thing.

Dev servers only answer requests whose Host header they recognise. It is a guard against DNS rebinding, and out of the box the list is localhost and little else. LocalCan forwards the hostname the browser used, so your app sees my-app.localcan.dev (or my-app.trylocalcan.com on the Free plan), my-app.local, or your custom domain, and refuses it.

The fix is to allow those hostnames in your framework. A leading dot means "the domain and every subdomain" in most of them, so .localcan.dev covers every Public URL (also called a tunnel) you create. Add your own domain if you use a custom one. If you would rather not touch the framework, rewrite the Host header in LocalCan instead (last section).

Vite and SvelteKit

Vite prints:

Text
Blocked request. This host ("my-app.localcan.dev") is not allowed.
To allow this host, add "my-app.localcan.dev" to `server.allowedHosts` in vite.config.js.
vite.config.js
JS vite.config.js
import { defineConfig } from 'vite'

export default defineConfig({
  server: {
    allowedHosts: ['.localcan.dev', '.trylocalcan.com', '.local'],
  },
})

A SvelteKit project is configured in this very file, so the block above is the whole fix there too.

Nuxt, Astro, and Angular are built on Vite as well and show the same message, but each one keeps the setting in its own config file rather than in vite.config.js. They have their own sections below.

Next.js

Next.js checks where a request came from (the browser's Origin header) rather than the Host header, and only for its own dev endpoints under /_next. The page renders, but the hot reload socket is refused and the terminal prints:

Text
Blocked cross-origin request to Next.js dev resource /_next/hmr from "my-app.localcan.dev".

Add the hostnames to allowedDevOrigins. Here * stands for one label, so *.localcan.dev matches every Public URL:

next.config.ts
TS next.config.ts
import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
  allowedDevOrigins: ['*.localcan.dev', '*.trylocalcan.com', '*.local'],
}

export default nextConfig

Angular

Angular's dev server is Vite under the hood, so the error text is Vite's and mentions vite.config.js. The setting lives in angular.json instead:

angular.json
JSON angular.json
{
  "projects": {
    "my-app": {
      "architect": {
        "serve": {
          "options": {
            "allowedHosts": [".localcan.dev", ".trylocalcan.com", ".local"]
          }
        }
      }
    }
  }
}

Nuxt

Nuxt shows Vite's message unchanged, which tells you to edit vite.config.js, a file a Nuxt project does not have. Put the same option in nuxt.config.ts instead, nested under the vite key:

nuxt.config.ts
TS nuxt.config.ts
export default defineNuxtConfig({
  vite: {
    server: {
      allowedHosts: ['.localcan.dev', '.trylocalcan.com', '.local'],
    },
  },
})

Astro

Astro has had its own server.allowedHosts since version 5.4, so nothing goes under a vite key here:

astro.config.mjs
JS astro.config.mjs
import { defineConfig } from 'astro/config'

export default defineConfig({
  server: {
    allowedHosts: ['.localcan.dev', '.trylocalcan.com', '.local'],
  },
})

Ruby on Rails

Rails shows a "Blocked hosts" page that names the host and the line to add. Put it in config/environments/development.rb:

config/environments/development.rb
Ruby config/environments/development.rb
config.hosts << ".localcan.dev"
config.hosts << ".trylocalcan.com"
config.hosts << ".local"

Django

Django raises DisallowedHost: "Invalid HTTP_HOST header: 'my-app.localcan.dev'. You may need to add 'my-app.localcan.dev' to ALLOWED_HOSTS." Django allows only localhost while ALLOWED_HOSTS is empty, so list that too once you set it:

settings.py
Python settings.py
ALLOWED_HOSTS = ['localhost', '127.0.0.1', '.localcan.dev', '.trylocalcan.com', '.local']

Other frameworks have an equivalent allow-list. Search their docs for "allowed hosts".

Rewrite the Host header in LocalCan instead

Tell LocalCan to send localhost as the Host header and the dev server never sees the public name. In the app, open the Public URL or local domain, click Request Headers, add a header named Host, and choose Set: Target Server Host from its menu. From the CLI:

Bash
localcan http 5173 -H "Host: {{target_host}}"

Or in the project file:

~/.localcan/projects/my-app.yml
YAML ~/.localcan/projects/my-app.yml
endpoints:
  - provider: localcan
    headers:
      - name: Host
        value: "{{target_host}}"

The {{target_host}} variable resolves to the hostname of the service target, localhost for http://localhost:5173. See Headers for the other modes and variables.

Two limits. Your app now believes it runs on localhost, so absolute URLs it builds from the request (redirects, OAuth callbacks, canonical links) point there too. And it does nothing for checks on the browser's Origin header, so Next.js still needs the config above.

© 2026 LocalCan™. All rights reserved.