
Test browser-facing APIs and WebSockets without sending the browser directly into a private network
Chrome's evolving Local Network Access protections can affect applications loaded from a public website that connect to localhost, a loopback address, or a private-network service. These checks are separate from CORS, mixed-content enforcement, and application-level WebSocket authorization, so solving one layer does not automatically solve the others. This guide explains the boundaries, shows how to diagnose failures, and outlines a safer testing architecture in which a Localtonet HTTP tunnel gives the local service a public HTTPS endpoint. Browser behavior, permission labels, rollout versions, and managed-browser policies remain version-sensitive and must be checked against current Chrome documentation before publication or fleet-wide deployment.
π What's in this guide
Why Chrome Local Network Access matters to developers
A common development pattern starts with a public web application and a companion service running on the user's computer. The public page may call an API at http://localhost, connect to a loopback address such as 127.0.0.1, or open a WebSocket to a private address on the local network. This is useful for device controllers, desktop helpers, testing agents, local AI services, network diagnostics, and development tools.
The same pattern also crosses an important security boundary. A public website should not receive unrestricted access to services that happen to be reachable from a visitor's private network. Browsers are therefore adding and refining controls around requests from public origins to loopback and private-network destinations. Depending on the Chrome or Chromium version, security context, destination, permission state, and deployment policy, the browser may ask the user for permission, reject the request, or expose a diagnostic in DevTools.
Exact rollout details are especially sensitive to browser versions. Prompt wording, settings-page labels, enforcement coverage, enterprise policies, and the treatment of particular request types can change between Chrome releases. Chromium-based browsers may also adopt a change on different schedules. Before shipping instructions to end users, validate them against the current official documentation for the browser versions in your supported fleet.
A Local Network Access decision controls whether the browser may attempt a network connection. It does not authenticate the user to your API, validate a WebSocket client, authorize an operation, or make an exposed service safe. Your application still needs appropriate authentication, authorization, origin validation, input validation, and least-privilege controls.
Local Network Access behavior becomes particularly confusing because several browser and server security mechanisms can fail at roughly the same point in an application. A failed fetch() request might be caused by local-network restrictions, mixed content, CORS, a TLS problem, an unavailable server, or rejected credentials. A failed WebSocket can originate from browser network policy, an invalid secure WebSocket URL, a rejected HTTP Upgrade handshake, an application origin check, or an incorrectly configured server.
The most productive way to troubleshoot is to identify each layer separately. Start with the destination the browser is actually contacting, then check the page's security context, the network connection, the HTTP or WebSocket protocol exchange, and finally the application's own authorization rules.
127.0.0.1, or ::1 targets the computer running the browser. It does not target the server that delivered the page.
Local Network Access, CORS, mixed content and WebSockets are different layers

It is tempting to group every browser networking error under CORS, but that often leads to the wrong fix. Local Network Access, CORS, mixed-content rules, TLS validation, and WebSocket handshake checks solve different problems. More than one can apply to the same request.
| Control or layer | Primary question | Typical responsibility | What it does not prove |
|---|---|---|---|
| Local Network Access | May this public page initiate access toward a local or private destination? | Browser permission and network security model | That the caller is authenticated or allowed to perform an operation |
| CORS | May browser script read a cross-origin HTTP response? | Browser enforcement based on server response headers | That the destination is publicly reachable or outside a local address space |
| Mixed content | May a secure page load or connect to an insecure resource? | Browser secure-context enforcement | That the server accepts the request or returns valid CORS headers |
| TLS | Can the client establish and validate an encrypted connection for the hostname? | Browser, certificate chain, endpoint, and TLS termination | That application credentials or permissions are valid |
| WebSocket handshake | Will the server accept the HTTP Upgrade request? | WebSocket server, reverse path, and application configuration | That subsequent messages are authorized or semantically valid |
| Application authorization | May this identity access this resource or operation? | Your API or WebSocket application | That the browser can reach the endpoint at all |
Local Network Access is destination-sensitive
The significant architectural detail is where the browser believes it is connecting. If JavaScript loaded from a public origin requests a loopback or private address, the browser can classify that as access from a less-private address space into a more-private one. Using a friendly public-looking hostname does not necessarily change the classification if DNS ultimately resolves that hostname to a loopback or private address.
This is why replacing 127.0.0.1 with a DNS record that still resolves to 127.0.0.1 is not a reliable solution. The visible hostname and the resolved network destination are separate pieces of information. Browser implementations can use the resolved address when applying local-network protections.
CORS controls HTTP response access
CORS applies when browser JavaScript makes an HTTP request across origins. An origin is defined by scheme, hostname, and port. A page at https://app.example.test and an API at another hostname are cross-origin even when both are public and use HTTPS.
The API must return CORS headers appropriate for the requesting origin and request type. Some requests trigger a preflight request using the OPTIONS method. Authentication headers, non-simple content types, and non-simple methods commonly affect whether a preflight is required. A tunnel does not synthesize an application's CORS policy, and changing the network path does not automatically allow the browser to read the response.
Avoid responding with an unrestricted origin policy merely to silence an error, especially when credentials or sensitive data are involved. Define the frontend origins that genuinely need access, allow only the required methods and headers, and test both preflight and actual responses.
Mixed content concerns secure and insecure schemes
A page loaded over HTTPS operates as a secure context. Browsers generally restrict active connections from that page to insecure resources. A request from an HTTPS page to an http:// API or a ws:// WebSocket may therefore be blocked before application-level behavior becomes relevant.
For a public HTTPS page, use an HTTPS API URL and a secure wss:// WebSocket URL where the browser-facing endpoint supports it. Do not disable browser security features as a routine development fix. That produces a machine-specific exception and hides problems that users will still encounter.
WebSockets start as HTTP but are not governed exactly like fetch
A WebSocket connection begins with an HTTP request asking the server to upgrade the connection. The server must recognize the route, accept the upgrade, and return the expected status and headers. After that handshake, the connection becomes a bidirectional WebSocket stream.
The browser includes an Origin header in the handshake. WebSocket servers should validate this origin where appropriate. CORS response headers are not a replacement for a WebSocket origin allowlist, and a successful HTTP API request does not prove that a WebSocket route is configured correctly.
Later console messages are often consequences of the original failure. Inspect the first blocked-request message, the Network panel entry, the resolved URL, and the WebSocket handshake details. This usually reveals whether the failure occurred before a request was sent, during TLS, during CORS preflight, or during the WebSocket upgrade.
How a public HTTPS tunnel changes the browser-facing architecture

With the direct-local pattern, the browser receives a public page and then attempts to connect inward to a loopback or private-network address. The browser is an active participant in that local-network transition. Local Network Access protections are designed around precisely this kind of boundary.
With Localtonet, our client application runs on the device that can reach the local service. The client establishes an outbound connection to a Localtonet relay server. An HTTP tunnel then provides a public URL that forwards traffic to the configured local IP address and port. The browser contacts the assigned public endpoint rather than requesting localhost or a private IP directly.
This changes the network path. It does not disable or bypass a browser permission. The browser is simply given a different destination. The public request reaches our relay, and the already-established outbound tunnel carries the traffic to the selected local target.
Because the Localtonet client initiates the relay connection outbound, the workflow does not require inbound router port forwarding, a public IP address, firewall changes, or VPN setup. The tunnel remains available only while the selected client device is connected and the tunnel is running.
This architecture is useful when a developer needs to test a public frontend against a local API, share a development endpoint with a remote tester, exercise a webhook or callback workflow, or validate browser behavior from a clean public origin. It also makes the security consequences more visible: the local application is now reachable through a public endpoint and must be prepared accordingly.
Do not expose an unauthenticated administrative API, development debugger, database console, or service with unsafe default routes. Restrict the application's capabilities, use authentication where appropriate, remove test credentials from client code, and stop the tunnel when the testing session ends.
Prerequisites and information to collect
Before creating a tunnel, verify the local application independently. Tunneling cannot repair a service that is not running, listening on the wrong interface, using an incorrect route, rejecting the expected host or origin, or failing its own startup checks.
Collect the following information:
- The local IP address reachable from the device running the Localtonet client.
- The actual listening port reported by the application or its configuration.
- An HTTP endpoint suitable for verification, such as an existing health, status, or version route.
- The WebSocket path, if the service exposes WebSockets.
- The public frontend origin that the API or WebSocket server should permit.
- The application's authentication method and the least-privilege test account or token strategy.
- A Localtonet device/auth token for the client that will run the tunnel.
- An available Localtonet relay server or region selected from the current dashboard.
Device tokens identify the client device and must be treated as secrets. Do not place them in browser JavaScript, source control, screenshots, support tickets, or article examples. Relay server codes and available regions can change by current product configuration, deployment, or plan, so select an available value from the dashboard rather than copying a hardcoded example.
The local bind address matters. A service bound only to loopback can normally be reached by a Localtonet client running on the same machine, provided the configured target uses the correct loopback address and port. If the client runs in another container, virtual machine, or physical device, that device's localhost refers to itself, not to the application host. In that case, configure a target address that is actually reachable from the client environment and secure that listener appropriately.
| Deployment arrangement | Meaning of localhost | Verification focus |
|---|---|---|
| Application and Localtonet client on the same host | The same operating-system network namespace in a conventional installation | Confirm the application's exact loopback port and route |
| Application in a container, client on the host | Container localhost and host localhost are normally different | Confirm published container networking and host reachability |
| Application on another LAN device | Client localhost refers to the client device, not the application device | Use a reachable private address and limit LAN exposure |
| Application and client in separate virtual machines | Each virtual machine has its own loopback interface | Check virtual networking, routing, and service bind settings |
Prepare and verify the local API or WebSocket service
Start the application normally
Use the project's documented startup procedure. Confirm that startup completes without errors and record the listening address and port reported by the application. Do not infer the port from a framework convention if the application's actual configuration says otherwise.
Test an HTTP route locally
From the same environment that will run the Localtonet client, request a known API, health, or status route using a browser, an HTTP client, or the application's own verification procedure. Confirm the expected status code and response body.
Test the WebSocket route locally
If WebSockets are part of the workflow, use a suitable WebSocket client or the application's local test page to verify the exact path and subprotocol requirements. Confirm that the connection opens and that at least one expected application message can be exchanged.
Review host and origin validation
Determine whether the application restricts HTTP hostnames, CORS origins, WebSocket origins, or trusted proxy behavior. Plan narrowly scoped changes for the assigned public origin instead of disabling these checks globally.
Enable application authentication
Protect sensitive routes before public exposure. Use the application's supported authentication mechanism, apply least privilege, and ensure secrets are not embedded in frontend code or WebSocket query strings unless the application explicitly documents a safe design.
If local HTTP succeeds but the local WebSocket fails, solve the WebSocket problem before adding the tunnel. Check the exact route, scheme, expected subprotocol, authentication requirements, and server logs. A normal HTTP response on the root path does not establish that the WebSocket upgrade path works.
If the API is designed only for same-origin use, decide whether to keep that model. One option is to have the public application server call the API server-side rather than letting browser JavaScript call it directly. Another is to configure a specific cross-origin browser policy. The correct choice depends on the application's trust model and cannot be safely replaced by a universal CORS setting.
Configure the Localtonet HTTP tunnel
Once the service works locally from the client device, create the public path. The workflow below follows the established Localtonet tunnel lifecycle without inventing version-specific dashboard fields. Names and available selections can vary with the current dashboard, client version, region, or plan.
Install and run the Localtonet client
Install the Localtonet client on the device that can reach the local API or WebSocket service, then run it. If the application is in a container or another machine, verify reachability from this device rather than only from the application's own environment.
Authenticate or select the client device
Use the device-specific auth token associated with the client that will carry the tunnel. Keep the token private and verify that the correct device is connected before continuing.
Select an available relay server
Choose a server or region currently offered in the Localtonet dashboard. Do not copy a server code from an old guide because availability can vary.
Create an HTTP tunnel for the local target
Configure the tunnel with the local IP address and the exact port verified earlier. HTTP tunnels can use a random subdomain, a selected subdomain where supported, or a custom domain. Exact custom-domain DNS requirements should be checked against current Localtonet documentation before configuration.
Start the tunnel
Start the newly created tunnel. Creation alone does not make it active. Wait until the selected device is connected and the tunnel is running.
Record the assigned public URL
Use the public HTTPS address shown for the running tunnel as the browser-facing API origin. Build any application route from that assigned origin without publishing private dashboard identifiers, auth tokens, or credentials.
For the current dashboard workflow and available HTTP tunnel options, consult the Localtonet HTTP tunnel documentation. The dashboard is authoritative for the server selection, assigned address, and options available to your account at configuration time.
If the public URL does not respond, confirm both the Localtonet device connection and the tunnel's running state. A saved tunnel configuration is not reachable until it has been started, and it becomes unavailable when the selected client disconnects or the tunnel stops.
Verify the public API and WebSocket path

Verification should progress from simple transport checks to browser-specific behavior. Testing everything through the full frontend immediately can hide the layer that failed.
1. Test a simple HTTPS request
Request a known route through the assigned public URL. Replace the placeholder hostname and path below with the actual URL and an existing route from your application:
curl -i https://PUBLIC_HOST/EXISTING_HEALTH_ROUTE
Confirm that the status and body match the local result. A different response may indicate a wrong route, an application host restriction, authentication behavior, or a target configuration problem. Do not assume that an HTTP 404 means the tunnel is offline. A 404 often means the connection reached an HTTP server but requested a route that server does not provide.
2. Test browser fetch behavior
After direct HTTPS verification succeeds, point the frontend at the public API origin. A minimal diagnostic request can help isolate browser behavior:
const apiUrl = "https://PUBLIC_HOST/EXISTING_API_ROUTE";
fetch(apiUrl, {
method: "GET",
credentials: "omit"
})
.then(async response => {
console.log("Status:", response.status);
console.log("Body:", await response.text());
})
.catch(error => {
console.error("Request failed:", error);
});
Choose the credential mode according to the application's actual authentication design. The example deliberately does not assume cookies or an authorization header. If the application uses credentials, configure its CORS policy carefully. Credentialed cross-origin requests have stricter requirements than unauthenticated public requests.
Inspect the Network panel for an OPTIONS request. If the preflight fails, compare the requested origin, method, and headers with the API's response. If no request appears at all, inspect the Console for a mixed-content, browser policy, DNS, or malformed-URL error.
3. Test the secure WebSocket URL
For a page loaded through HTTPS, construct the browser-facing WebSocket URL with the secure wss:// scheme and the application's existing WebSocket path:
const socket = new WebSocket("wss://PUBLIC_HOST/EXISTING_WEBSOCKET_PATH");
socket.addEventListener("open", () => {
console.log("WebSocket connected");
});
socket.addEventListener("message", event => {
console.log("WebSocket message:", event.data);
});
socket.addEventListener("error", event => {
console.error("WebSocket error:", event);
});
socket.addEventListener("close", event => {
console.log("WebSocket closed:", event.code, event.reason);
});
This code tests connection events but does not assume an application message format. Send only messages documented by your service. If the server requires a WebSocket subprotocol, authentication exchange, or initial subscription message, add it according to that application's specification rather than guessing.
In Chrome DevTools, select the WebSocket request in the Network panel. Review the handshake URL, request headers, response status, frames, close code, and timing. A rejected upgrade differs from a connection that opens and then closes because the application did not receive an expected authentication message.
4. Compare direct-local and public-endpoint behavior
Run a controlled comparison:
- Load the frontend from its intended public HTTPS origin.
- Attempt the original direct-local URL and record the first browser diagnostic.
- Attempt the Localtonet public HTTPS or secure WebSocket URL.
- Compare whether a request was sent, whether TLS completed, and whether the application returned a response.
- Check application logs for both attempts.
If the direct-local request is affected by Local Network Access behavior but the public endpoint reaches the service, the difference is architectural: the browser is no longer initiating a connection to a loopback or private destination. This does not imply that the browser's security controls were disabled.
5. Test a clean browser state
Browser permissions can be remembered per origin, and cached application configuration can preserve old URLs. Test with a clean profile or carefully reset the relevant site data and permissions using the current browser UI. Do not publish exact menu labels until they have been checked for the Chrome versions your audience uses because those labels can change.
Also test at least one browser outside your normal development profile. Extensions, enterprise configuration, manually changed flags, cached service workers, and previously granted permissions can make a developer's browser behave differently from a user's installation.
Secure the API and WebSocket before sharing the URL
A local service often begins with assumptions that stop being valid when it receives a public endpoint. It may trust every caller, expose detailed error messages, provide a debug console, allow broad filesystem operations, or bind administrative and user-facing routes to the same listener. Review those assumptions before starting the tunnel.
Do not put durable secrets in frontend code
Any secret shipped to browser JavaScript can be inspected by the user and may appear in logs, source maps, network captures, browser storage, or error reports. Prefer a user authentication flow or a short-lived credential design appropriate to the application. Avoid credentials in URLs because URLs can be copied, logged, retained in history, and included in diagnostic output.
Validate WebSocket clients after connection
A successful WebSocket handshake should not grant unlimited access. Authenticate the session using the application's supported design, enforce authorization for subscriptions and commands, and handle token expiry or revocation. Limit message sizes and reject unexpected message types. Close connections cleanly when authorization fails.
Use narrow CORS rules
Define the exact frontend origins that need browser access. Permit only required methods and headers. If cookies or other browser-managed credentials are involved, review cross-site cookie attributes, CSRF protections, and the application's credential model together. CORS is not a CSRF defense by itself, and allowing an origin is not the same as authorizing every user from that origin.
Keep development and production boundaries clear
A temporary tunnel is valuable for testing, demonstrations, and controlled remote access, but it should not silently turn a development process into an unmanaged production service. Document who owns the tunnel, when it should run, what data may pass through it, and how access is revoked. Recheck plan, region, and deployment requirements in the current dashboard instead of assuming that every capability is available in every subscription.
Troubleshooting APIs, CORS and WebSocket connections
| Symptom | Likely layer | What to inspect |
|---|---|---|
| The local URL works on the server but not from the Localtonet client device | Binding, routing, container, VM, or firewall path | Listening address, network namespace, target IP, and local reachability from the client device |
| The public URL does not connect | Tunnel lifecycle or local target | Client connection, running state, selected device, target address, target port, and application process |
| The public URL returns 404 | Application routing | Exact path, base path, method, and whether the request reached the intended application |
| Command-line HTTP works but browser fetch fails | CORS, credentials, mixed content, or browser policy | Console message, preflight request, response headers, page scheme, and requested origin |
| WebSocket handshake returns a normal HTML page | Wrong route or application fallback | WebSocket path, server route, host handling, and upgrade support |
| WebSocket opens and immediately closes | Application protocol or authorization | Close code, server logs, subprotocol, authentication, and required first message |
HTTPS page attempts ws:// or http:// |
Mixed content | Generated client URL and environment configuration |
| Direct localhost request shows a local-network permission issue | Browser Local Network Access | Chrome version, destination resolution, page security context, site permission, and managed-browser policy |
The service is listening on the wrong interface
Confirm the application's actual listener. If both the application and Localtonet client run on the same host, a loopback listener may be sufficient. If they run in separate containers, machines, or network namespaces, loopback is not shared. Change the application binding only when necessary, and avoid exposing it broadly to the LAN without appropriate host firewall and application controls.
The tunnel targets the wrong port
Development tools sometimes use separate ports for the frontend, API, and WebSocket server. Proxy configuration can make them appear to share one origin during local development even when separate processes handle them. Verify which process receives the desired route and configure the local target accordingly. Do not select a port merely because it is common for the framework.
The application rejects the public host
Some frameworks validate the HTTP Host header to reduce DNS rebinding and accidental exposure. If logs show a rejected host, add the assigned hostname using the framework's documented allowlist mechanism. Do not disable host validation globally. The exact setting is framework-specific and should not be guessed.
The CORS preflight fails
Verify that the server handles OPTIONS on the requested route and returns headers consistent with the actual frontend origin, method, and request headers. Redirects, authentication middleware, and generic error handlers can interfere with preflight requests. Inspect the preflight response itself rather than changing unrelated browser settings.
The WebSocket route fails while HTTP works
Check that the browser uses wss:// for a public HTTPS page and that the WebSocket path is exact. Inspect the handshake response. A 401 or 403 points toward authentication, authorization, or origin validation. A 404 points toward routing. A successful upgrade followed by an immediate close usually indicates an application-level requirement, such as a missing subprotocol or authentication message.
The page still contains localhost references
Search runtime configuration, generated bundles, environment-specific configuration, service workers, and browser storage for the old local URL. A frontend may update its HTTP API base URL while retaining a separate localhost WebSocket URL. Clear stale application caches carefully and verify the final URL in DevTools.
The browser behavior differs across users
Compare browser versions, Chromium variants, site permissions, enterprise policies, extensions, and secure-context status. Local Network Access is an evolving browser area, so do not assume identical behavior across Chrome, Edge, Brave, Opera, Firefox, Safari, or older releases. Confirm current behavior with official documentation and a representative test matrix before giving end users exact prompt instructions.
Disabling security features, using experimental flags, or launching a browser with unsafe command-line options may hide the actual deployment problem. Such exceptions are unsuitable for normal users and can create serious exposure. Fix the URL architecture, secure context, application policy, and authentication model instead.
Frequently asked questions
Is Chrome Local Network Access the same as CORS?
No. Local Network Access concerns a browser request crossing toward a loopback or private-network destination. CORS controls whether browser JavaScript may access a cross-origin HTTP response. A request can pass one control and fail the other, so each layer must be diagnosed independently.
Does a Localtonet tunnel disable Chrome's local-network protections?
No. With an HTTP tunnel, the browser is given a public HTTPS destination instead of being instructed to connect directly to localhost or a private IP. Our client establishes an outbound relay connection and forwards the public request to the configured local target. This changes the network architecture rather than disabling a browser security control.
Do I still need CORS after using a public HTTPS tunnel?
Usually yes when browser JavaScript calls an API on a different origin. The application must return a CORS policy suitable for the intended frontend origin, methods, headers, and credential model. A tunnel does not automatically define or broaden the API's CORS policy.
Should an HTTPS page use ws:// or wss:// for the WebSocket?
Use a secure wss:// browser-facing URL for a page loaded over HTTPS. Attempting an insecure ws:// connection from a secure page can be blocked as mixed content. The path, subprotocol, authentication, and origin policy must still match the WebSocket application's requirements.
Can I use a public DNS name that resolves to 127.0.0.1?
Do not rely on that as a Local Network Access workaround. Browser network classification can consider the resolved address, not only the visible hostname. A hostname that resolves to loopback can still be treated as a loopback destination.
Why does curl work when fetch fails?
Command-line HTTP clients do not enforce browser CORS or mixed-content rules. A successful command-line request proves useful transport and application behavior, but browser JavaScript can still fail because of CORS, secure-context policy, permissions, credentials, or frontend configuration.
Does the Localtonet client need to run on the same machine as the API?
It must run on a device that can reach the configured local IP address and port. Running it on the same machine is often straightforward, but the target can also be a service reachable from the client device. Remember that localhost always refers to the environment in which the client itself is running.
Does creating a tunnel make it immediately available?
No. Creating the tunnel saves its configuration, but the tunnel must be started. It is available only while the selected client device is connected and the tunnel is running. Stop or delete it when public access is no longer required.
Which Chrome versions show a Local Network Access prompt for WebSockets?
Enforcement and permission behavior are version-sensitive and can change during staged browser rollouts. Chromium-based browsers may also adopt changes on different schedules. Verify the current official Chrome documentation, release status, managed-browser policies, and prompt behavior immediately before publishing user instructions or deploying an enterprise configuration.
Test your local API through a public HTTPS endpoint
Run the Localtonet client on a device that can reach your verified local service, create an HTTP tunnel, and test the assigned public URL from your browser. Secure the application first, keep device tokens private, and stop the tunnel when the testing session is complete.
Get Started Free β