TypeScript

Fix Invalid options object: allowedHosts[0] with CRA proxy

Find which of the two triggers crashes npm start, pick a fix that still proxies your API calls, and avoid the http-proxy-middleware v3 path trap.

Mahdi Ben RhoumaMahdi Ben Rhouma
7 min read

TL;DR#

react-scripts start (Create React App 5) crashes with this schema error when two things are true at once: package.json has a "proxy" field, and react-scripts cannot work out a private LAN address for the dev server. That happens when HOST names a specific host (anything other than 0.0.0.0 or ::), or when the machine has no private IPv4 address (no network, or only a public IP). In both cases it hands webpack-dev-server an allowedHosts list whose only item is undefined. The fix: unset HOST if you did not mean to set it, or move the proxy into src/setupProxy.js with http-proxy-middleware version 2. Only the first keeps the dev server's host check, which protects your backend from DNS rebinding.

The error, as npm start prints it with react-scripts 5.0.1:

text
Attempting to bind to HOST environment variable: localhost
If this was unintentional, check that you haven't mistakenly set it in your shell.
Learn more here: https://cra.link/advanced-config
 
Invalid options object. Dev Server has been initialized using an options object that does not match the API schema.
 - options.allowedHosts[0] should be a non-empty string.

The Stack Overflow question shows the same two lines under yarn start. The asker's package.json pins "react-scripts": "5.0.0" and the app starts again as soon as "proxy": "http://localhost:6000" is deleted.

Why react-scripts builds an invalid allowedHosts#

Three pieces of Create React App's source explain the crash. All links point at the same commit.

  1. start.js calls prepareUrls() with the host to bind (the HOST variable, or 0.0.0.0 when it is unset). It then passes the result's LAN address to the dev server config: createDevServerConfig(proxyConfig, urls.lanUrlForConfig).
  2. prepareUrls() only fills in that LAN address when the host is 0.0.0.0 or ::. Even then, it keeps the address only if it falls in one of the three private IPv4 ranges. Any other host, a public IP or no IPv4 at all leaves it undefined.
  3. webpackDevServer.config.js turns the host check off unless a proxy is configured: allowedHosts: disableFirewall ? 'all' : [allowedHost], where disableFirewall is true when there is no proxy or when DANGEROUSLY_DISABLE_HOST_CHECK is 'true'.

With a proxy and no LAN address, the dev server receives allowedHosts: [undefined]. react-scripts 5.0.0 and 5.0.1 both depend on webpack-dev-server ^4.6.0 (npm registry). The version 4 options schema (v4.15.2 options.json) requires every allowedHosts item to be a string of at least one character. Validation fails before the server binds a port.

The host check exists for a reason. The CRA proxy documentation says that enabling proxy opts you into stricter host checks, to protect the backend from DNS rebinding attacks. The same page suggests setting HOST to your public development host to get past "Invalid Host header". Combined with proxy, that suggestion now triggers this crash. The bug is tracked in create-react-app#11762, opened against 5.0.0 and still open. 5.0.1 is still the latest tag on npm, and the repository README marks Create React App as deprecated.

Reproducing both triggers#

Both paths were reproduced on 20 October 2026 in a node:20-alpine container (Node 20.20.2, react-scripts 5.0.1, webpack-dev-server 4.15.2). The test app is a minimal CRA app with "proxy": "http://localhost:6000" and a small JSON backend on port 6000.

With HOST=localhost, npm start fails with the output above. A HOST set to the container's own hostname fails the same way. That is the setting the CRA docs suggest for "Invalid Host header".

As a negative control, the same app with no HOST starts and proxies, because the container's bridge address (172.17.0.3) is private:

text
  On Your Network:  http://172.17.0.3:3000
{"from":"backend:6000","path":"/api/todos"}

The second trigger appears once the same container is disconnected from its network. address.ip() (the helper prepareUrls() calls) then returns nothing, and npm start crashes with no HOST set at all:

text
$ node -p "require(\"address\").ip()"
undefined
Invalid options object. Dev Server has been initialized using an options object that does not match the API schema.
 - options.allowedHosts[0] should be a non-empty string.

The Stack Overflow asker shows no HOST, so you cannot tell from the question which trigger hit them. Check yours first:

bash
echo "HOST=$HOST"
grep -s HOST .env .env.local .env.development .env.development.local

react-scripts prints "Attempting to bind to HOST environment variable" whenever HOST is set. If the value is a hostname or a specific address, you have the first trigger. If HOST is unset, or set to 0.0.0.0 or ::, look at the network instead: an offline machine, or one whose only IPv4 address is public.

Fix#

  1. Unset an unintended HOST. Remove the line from .env (or the shell profile), then start again:

    bash
    unset HOST
    npm start

    This is the negative-control run above: the proxy keeps working. It only helps with the first trigger and only if you do not need a specific bind address.

  2. Move the proxy into src/setupProxy.js (the accepted answer, and CRA's documented "Configuring the Proxy Manually"). Delete the "proxy" line from package.json, then install the middleware. Pin major version 2, the line react-scripts' own webpack-dev-server uses:

    bash
    npm install http-proxy-middleware@2 --save
    js
    // src/setupProxy.js
    const { createProxyMiddleware } = require('http-proxy-middleware');
     
    module.exports = function (app) {
      app.use(
        '/api',
        createProxyMiddleware({
          target: 'http://localhost:6000',
          changeOrigin: true,
        })
      );
    };

    react-scripts loads this file with require() when the dev server starts; you do not import it anywhere. Because package.json no longer has a proxy, disableFirewall is true and allowedHosts becomes 'all'. So this fix also switches off the host check that the proxy field would have turned on. That is the trade-off the docs warn about. The threat is DNS rebinding: while the dev server runs, a malicious page you open in your own browser can send requests to it, and through the proxy to your backend. No network setting prevents that. Stop the dev server when you are not using it, and if you do not need a specific HOST, prefer fix 1, which keeps the check on.

  3. Or keep proxy and set DANGEROUSLY_DISABLE_HOST_CHECK=true in .env.development.local. This works with either trigger, and the reproduction proxied /api/todos normally with HOST=localhost still set. The CRA docs call it dangerous and "exposes your machine to remote code execution from malicious websites": any website you visit while the dev server runs can reach your backend through it. Turn the variable off as soon as you no longer need it, and prefer fix 1 or fix 2.

Verify the fix#

Start the dev server, then request an API path with a non-HTML Accept header. The CRA proxy only forwards requests without text/html in Accept:

bash
curl -s -H 'Accept: application/json' http://localhost:3000/api/todos

The backend's JSON should come back. In the reproduction (which used busybox wget with the same Accept header), the setupProxy fix with http-proxy-middleware 2.0.10 returned:

text
[HPM] Proxy created: /  -> http://localhost:6000
{"from":"backend:6000","path":"/api/todos"}

One email a month — the latest explained errors

What the site published that month — mostly Next.js, Supabase and Node errors, explained. You confirm by email; unsubscribe any time in one click.

Two fixes that start the server but break the proxy#

npm install http-proxy-middleware without a version. On Node 20 this installed 3.0.7. Version 3 no longer rewrites req.url, so when the middleware is mounted with app.use('/api', …) the backend receives the path without its mount prefix:

text
`-- http-proxy-middleware@3.0.7
{"from":"backend:6000","path":"/todos"}

The v3 migration guide says the mount path must now go in the target (target: 'http://localhost:6000/api'). The current latest on npm is version 4. Its release notes list breaking changes of their own, including a minimum of Node 22.15.0 and an ESM-only package. Pinning @2 keeps the code from the CRA docs and the accepted answer working as written.

Wrapping proxy in an "options" object (the second answer on the question). react-scripts reads require(paths.appPackageJson).proxy, the top-level key only. So the wrapper simply hides the proxy, and the server starts because allowedHosts falls back to 'all'. With HOST=localhost set, the reproduction started cleanly and the API call failed:

text
$ wget -qO- -S --header=Accept:application/json http://[::1]:3000/api/todos
  HTTP/1.1 404 Not Found

One more detail from the reproduction: with HOST=localhost on Node 20, the dev server listened on ::1, so a request to 127.0.0.1:3000 was refused. If curl fails after a successful start, try localhost or [::1] before you suspect the proxy.

When the browser shows a different error#

Once npm start runs, the next errors are usually in the browser, not the terminal. A page that loads with "Invalid Host header" means the host check is on and you are browsing from a hostname it does not know (a cloud editor, a custom domain). Setting HOST to that name now crashes with proxy, so use the setupProxy fix. If you drop the proxy entirely and call the API on its own port, the browser enforces CORS instead; the CORS fix for calls from localhost:3000 explains which header the backend must send.

If the app runs in Docker, the network trigger shows up in a different form. A container on the default bridge network gets a private address, but --network none, or a custom network whose subnet is outside the private ranges, leaves react-scripts without one. For two other CRA-in-Docker problems, see the hot reload fix for WSL2 and the missing public/index.html fix. An old react-scripts 4 project upgraded to a recent Node can hit error:0308010C:digital envelope routines::unsupported first. That page covers the webpack 4 hash and the react-scripts 5 upgrade that removes it, and once the project is on react-scripts 5, a proxy field can then produce the allowedHosts crash above.

Whichever fix you choose, it is a workaround in a tool that will not get another release. For a new project, the CRA README points to the frameworks listed on react.dev. Until you migrate, setupProxy.js with http-proxy-middleware 2 is the fix that keeps both the server and the proxy working.

Frequently Asked Questions

|

Have more questions? Contact us

Still stuck? Paste your error.

Paste the error message or stack trace — we check it against every fix and tool on the site.

Matching happens in your browser. When you search, a cleaned error signature is sent so we can spot errors we do not cover yet — never the raw text, your IP address or your e-mail. Do not paste passwords or keys. Details

Written by

Mahdi Ben Rhouma
Mahdi Ben Rhouma

Full-Stack Dev — Next.js & Supabase

Solo developer building SaaS products with Next.js and Supabase. Writing about production patterns the official docs skip.

Remote