Skip to main content
← All guides

Fix webpack-dev-server “Invalid Host header” through a tunnel

Content updated October 25, 2026.

Why does webpack-dev-server say “Invalid Host header” when I open my app through a tunnel?

webpack-dev-server rejects unknown Host headers with “Invalid Host header”. Add the tunnel domain, such as .qurl.site, to devServer.allowedHosts, never all. Set client.webSocketURL to auto://0.0.0.0:0/ws for hot reload behind HTTPS tunnels. With qURL™, your app stays on your machine with no public URL, and each link opens once and expires on its own. If you later need one permanent public hostname, ngrok’s dev domain fits.

What you need

  • A webpack-dev-server 5 or 6 app, opened through a tunnel or qURL link
  • Your webpack.config.js

Steps

  1. Read the error

    The page answers 403 with the plain text “Invalid Host header”. The hot reload socket is checked separately: it connects, then the server sends “Invalid Host/Origin header” and closes it, so look in the browser console rather than at a status code.

  2. Allow the tool’s domain, keep the server on 127.0.0.1, and point the socket at the page

    A leading dot in allowedHosts allows the domain and every subdomain. For qURL use the domain, not the exact host from the error: the hot reload socket also checks the browser’s Origin, which is r_….qurl.site while the page’s host is c-….qurl.site. The .qurl.site entry also permits HMR origins from other qURL publishers; share only with people you trust and stop the dev server after the review. host: 127.0.0.1 matters because, with no host set, webpack-dev-server 5 and 6 listen on every network interface. webSocketURL with auto and port 0 makes the hot reload client use the page’s own protocol, host and port, which webpack’s docs recommend behind a proxy; by default the client asks for the dev server’s port, such as wss://r_….qurl.site:8080/ws, which a tunnel on port 443 does not serve.

    // webpack.config.js (merge into your existing config)
    module.exports = {
      devServer: {
        host: '127.0.0.1',
        allowedHosts: ['.qurl.site'], // ngrok: 'your-domain.ngrok-free.app'
        client: { webSocketURL: 'auto://0.0.0.0:0/ws' },
      },
    }
  3. Restart the dev server and share it

    webpack-dev-server reads its config at start-up. Each qurl share link opens once and expires within 24 hours.

    qurl publish http://127.0.0.1:8080
    qurl share <CRID>

What the recipient sees

With the domain allowed, the page loads through the link.

Which tool should carry your webpack dev server?

OptionChoose it whenTrade-off
qURLqURL fits when particular people should try the app: one .qurl.site entry covers the page and the hot reload socket for every qURL link, with no account.The .qurl.site entry also trusts HMR origins from other qURL publishers; share only with people you trust and stop the dev server after the review.
ngrokngrok fits when you want one stable URL: your ngrok dev domain stays the same, so one exact allowedHosts entry lasts.ngrok needs an account and authtoken, the URL is public, and free-plan browser visitors see a warning page first.
Cloudflare Quick TunnelsA Cloudflare Quick Tunnel fits a no-account look: .trycloudflare.com covers the hostname that changes every run.A Cloudflare Quick Tunnel URL is public unless you set --allowed-mail, and Quick Tunnels do not support Server-Sent Events.

Troubleshooting

If you seeDo this
The page loads but edits never arrive, and the console shows a failed wss://…:8080/ws connectionThe hot reload client is asking for the dev server’s port through the tunnel. Set client.webSocketURL to auto://0.0.0.0:0/ws and restart.
The console shows “Invalid Host/Origin header” though the page loadsThe socket’s Origin is not allowed. Through qURL, list .qurl.site rather than the exact c-….qurl.site host, because the browser’s origin is r_….qurl.site.
Setting allowedHosts to all made it workall turns the host check off, which webpack’s docs warn leaves the server open to DNS rebinding attacks. List the domains you need instead.
qurl publish says the local app is not accepting TCP connectionsStart the app first and check that curl works on the same address. qurl publish connects to localhost as 127.0.0.1, so if your app listens only on IPv6 (on macOS or Linux, lsof -iTCP -sTCP:LISTEN -nP shows [::1]), publish 'http://[::1]:PORT' instead, or start the dev server with --host 127.0.0.1 and publish that address. Neither requires listening on your LAN. Keep the quotes: zsh treats the brackets as a pattern.
The recipient sees “Access link invalid”The link was already used or has expired. Run qurl share again and send the new link.
The recipient sees “Resource not found”The resource was deleted after the link was made. Publish the app again, then run qurl share and send the new link.
publish rejects http://127.0.0.1 with “only HTTPS URLs are allowed”, or asks you to log inYou have a CLI older than 3.0. On macOS run brew update, then brew upgrade qurl; elsewhere install the latest release again. After updating, qurl --version should print 3.4.1 or later.
publish prints no Access: line, or the Access: line says public and you did not ask for publicWith no Access: line, the CLI is older than 3.4.1: on macOS run brew update, then brew upgrade qurl; elsewhere install the latest release again. A resource that an older CLI published is public unless you published it with --private, and anyone who has the CRID of a public resource can open it. To make a public resource private, run qurl delete <CRID> and publish again with qurl 3.4.1 or later. The new resource is private and has a new CRID.
publish says this Linux session “can’t reach a systemd user manager”You are in a container or sandbox without a user service manager. Run qurl publish with --foreground; sharing lasts as long as that process runs.
publish says “Quota Exceeded” with “protected resource limit reached”, or “Your qURL account has reached a plan limit”You can see this without an account: a device with no account can keep up to 3 active resources, with links that last up to 24 hours. Run qurl list --status active, then qurl delete <CRID> for any you no longer need (it asks you to confirm; --yes skips that). qurl stop keeps the resource, so it does not free a slot.

Frequently Asked Questions

What about Create React App?

Create React App is deprecated and runs webpack-dev-server 4. Its docs fix “Invalid Host header” by naming the host in HOST in .env.development, and set where the hot reload socket connects with WDS_SOCKET_HOST and WDS_SOCKET_PORT. They call DANGEROUSLY_DISABLE_HOST_CHECK dangerous.

Which versions does this cover?

This configuration covers webpack-dev-server 6 and 5. Use a leading-dot entry for the domain and client.webSocketURL for hot reload through an HTTPS link. webpack-dev-server 6 needs Node.js 22.15 or later.

Who else can reach my app while the link is open?

Opening the link admits the network address it was opened from for the session, which lasts 1 hour by default. Other devices that share that public IP address, such as people on the same office or café network or VPN, can reach the app until the session ends. By default a link admits whoever opens it; access rules set through the API, such as IP allow and deny lists, can restrict that. Keep your app’s own sign-in for anything sensitive.

Publish your app now

Install the CLI, then run one command. No sign-up, no API key.

Install the CLI ↗

Pilot LayerV

Tell us what to protect, and we’ll scope a pilot.

Enterprise starts with a scoped, paid pilot on one application, typically 60 to 90 days, credited in full toward year one if you convert within 60 days. Then an annual package sized to what you protect. Not per seat.