Put a local dev server or API online
Give the app running on your own laptop a public HTTPS address, the way a tunnelling tool like ngrok does — for webhooks that have to call you back, for showing work in progress, and for testing on a real phone. The address belongs to your account, so it is the same every time.
What this does
A mapping does not have to point at a router. Install the Windows, Mac or Linux agent on the machine you develop on, point a mapping at 127.0.0.1 and the port your framework prints on startup, and the app you are building answers on a public HTTPS address from anywhere.
Nothing about your code has to change. The dev server keeps listening on localhost, keeps hot-reloading, and keeps its breakpoints — requests simply arrive from the internet as well as from your browser.
The address is part of your MikRouter account, so it is the same every time— it does not change when the dev server restarts, when your laptop reboots, or when you come back tomorrow. You can paste it into a payment gateway once and leave it there.
Before You Start
- A MikRouter account with an agent created under Agents — keep its
tunnel hostandtokennearby. - The agent installed and connected on your development machine — Windows, Mac or Linux. Its card under Agents should read Online before you go on.
- A dev server you can start, and the port it listens on —
3000,5173,8000, whatever it prints. - Not an ESP32.A board cannot run your code, and it holds only a handful of connections at once — one modern web page uses more than that on its own. Use a computer agent for this.
How the request gets to you
Anyone, anywhere
https://my-api
A browser, a phone on mobile data, or a payment gateway's servers.
MikRouter agent server
Provides the certificate
Then hands the request down the tunnel your agent already dialled out and is holding open.
Your machine
127.0.0.1:3000
The agent connects to your dev server exactly as your own browser does.
Nothing is listening on the internet and no port is forwarded. Your machine dialled out, so this works behind CGNAT, on hotel Wi-Fi, and on a network you do not control.
Start your dev server and note the port
Run it the way you always do. Leave it on localhost— you do not need 0.0.0.0 or any special flag while the agent is on the same machine.
$ npm run dev ▲ Next.js 16.0.0 - Local: http://localhost:3000 ✓ Ready in 1.4s POST /api/webhooks/stripe 200 in 38ms host: my-api.test.mikrouter.comHere the port is 3000. Vite prints 5173, Django 8000, Rails 3000. Whatever it says is what goes into the mapping.
Open your agent and add a Web mapping
Go to Agents, open the agent running on this machine, and add a mapping under Mappings.
| Field | Value | Note |
|---|---|---|
| Kind | Web (HTTP/S) | Raw TCP is for things that are not web pages |
| LAN host | 127.0.0.1 | the agent is on this same computer |
| LAN port | 3000 | the port from step 1 |
| Label | Checkout API — dev | for you; it does not appear in the address |
| Device uses HTTPS | off | tick it only if your dev server itself serves https:// |
| Address scheme | HTTPS (recommended) | MikRouter provides the certificate |
| Subdomain | my-api | answers at my-api.test.mikrouter.com |
Mappings
Web 0/10TCP 0/3New mapping
Device uses HTTPS — off. Tick it only if your dev server itself serves https:// on that port.
Require access password — off, because this one receives webhooks. Tick it for an app you are only showing to people.
Use 127.0.0.1 rather than localhost. On some systems localhostresolves to IPv6 first, and plenty of dev servers only listen on IPv4 — the numeric form removes the question.
Open the address
The mapping appears in the list with its full address. Open https://my-api.test.mikrouter.com in any browser, on any network.
If your app appears, you are done — skip to Webhooks and callbacks below. If instead you get "blocked request", "invalid host header", or a bare error page from your framework, that is step 4 and it is expected.
Let your framework accept the public hostname
MikRouter forwards the hostname unchanged, so your dev server is asked for my-api.test.mikrouter.com and not localhost. Several frameworks refuse a hostname they were not told about — it is a deliberate protection in them, not a fault in the tunnel. Add your address to the allow-list and restart the dev server.
The symptom is a cross origin request warning, and blocked dev-only requests such as hot reload.
export default {
allowedDevOrigins: ["my-api.test.mikrouter.com"],
};Only the development server checks this. A production build does not use allowedDevOrigins at all.
The symptom is a plain-text page reading "Blocked request. This host is not allowed." — and it names the fix in the same message.
export default {
server: {
allowedHosts: ["my-api.test.mikrouter.com"],
},
};Hot reload rides on a WebSocket, which MikRouter passes through untouched, so it reconnects on its own once the host is allowed.
The symptom is "Invalid HTTP_HOST header" with a DisallowedHost traceback.
ALLOWED_HOSTS = ["my-api.test.mikrouter.com", "127.0.0.1", "localhost"] CSRF_TRUSTED_ORIGINS = ["https://my-api.test.mikrouter.com"]
CSRF_TRUSTED_ORIGINS is the second half people miss: without it the pages load but every form POST comes back 403.
The symptom is a full-page "Blocked hosts" screen from ActionDispatch::HostAuthorization.
config.hosts << "my-api.test.mikrouter.com"
Nothing to do. Express, Fastify, FastAPI, Flask, Laravel, Go and Spring have no host allow-list in development — they serve whatever hostname arrives, so the mapping works as soon as you add it.
Angular. Add the address to the allowedHosts option of the serve target in angular.json.
Create React App. Put DANGEROUSLY_DISABLE_HOST_CHECK=true and WDS_SOCKET_PORT=443 in .env— the second one is what makes hot reload reconnect over the public HTTPS address.
If your framework is not listed and you get an error page mentioning the host or origin, search its documentation for "allowed hosts" — that is the setting, whatever it is called.
Point your app at its own public address
Anything that builds an absolute URL needs to know the address the outside world uses, or it will emit links back to localhostthat only work on your desk. Set whichever your stack uses — APP_URL, SITE_URL, NEXTAUTH_URL, PUBLIC_BASE_URL— to the mapping's address.
APP_URL=https://my-api.test.mikrouter.com
This is also what fixes OAuth sign-in during development: the redirect URL you register with the provider and the one your app generates have to be the same public address.
Webhooks and callbacks
This is the reason most developers want a tunnel at all. A payment gateway, GitHub, a messaging platform or your own second service has to call you, and it cannot call localhost. Point it at the mapping and the request lands in the code running on your machine — breakpoints, logs and all.
https://my-api.test.mikrouter.com/api/webhooks/stripe
| Field | Value | Note |
|---|---|---|
| Require access password | off | essential — see below |
| Address scheme | HTTPS (recommended) | most providers refuse a plain http:// endpoint |
Because the address is stable, you register it once. Restart the dev server, reboot, come back next week — the same URL still reaches you, and there is no new address to go and paste in.
Never tick "Require access password" on a webhook mapping
The access gate is a sign-in page. A payment provider's servers cannot sign in, so every callback gets the unlock page instead of your endpoint, your code never runs, and the provider records a failed delivery. Nothing in your logs explains it, because the request never reached your app.
Leave webhook mappings open and let the provider's own signature checkbe the guard — verifying the signing secret is what that mechanism is for, and it is stronger than a shared password.
Databases, SSH and anything that is not a web page
Postgres, MySQL, Redis, an SSH session or a plain TCP service are not HTTP, so a subdomain cannot carry them. Choose Raw TCP instead and MikRouter gives you an address and port rather than a hostname.
| Field | Value | Note |
|---|---|---|
| Kind | Raw TCP | no subdomain, no certificate, no unlock page |
| LAN host | 127.0.0.1 | same machine as the agent |
| LAN port | 5432 | Postgres here; 22 for SSH, 6379 for Redis |
| Public port | assigned for you | drawn from 50000–65535 — you cannot pick it |
Connect to the port shown on the mapping:
psql -h test.mikrouter.com -p 54321 -U postgres
ssh -p 54322 you@test.mikrouter.com
A raw TCP mapping has no unlock page
The access password protects web mappings only. On a TCP mapping the service's own login is the only thing in front of it, on a port the whole internet can reach. Never expose a database that still has a default or empty password, and disable the mapping when you are done with it.
The public port is assigned, not chosen — and neither the port nor the kind can be edited afterwards. If you need a different one, delete the mapping and add it again.
Keeping it private
The address is public and guessable enough to be found. An unfinished app with test data, an admin panel with no login yet, or a debug toolbar showing environment variables should not be sitting there in the open.
- Tick "Require access password" on any mapping a machine does not need to reach. MikRouter then asks for your account access password before it shows anything. Set that password first, under Agents — a protected mapping with no password set stays shut rather than falling open.
- Switch the mapping off when you finish for the day. The toggle on the mapping takes the address offline and keeps its settings, so turning it back on tomorrow is one click and the address is unchanged.
- Or stop the tunnel. Stopping the agent from its settings window takes every mapping on that machine offline at once.
- One mapping per service. A front end on
5173and an API on8000are two mappings and two addresses. Free and Starter allow 10 web and 3 TCP mappings per agent, Plus allows 20 and 6.
When it does not work
"Blocked request", "Invalid host header", or a framework error page
Your dev server is refusing the public hostname. That is step 4 above — add the address to your framework's allow-list and restart it.
The page never loads, and nothing appears in your dev server's log
The agent could not reach the port. Check the dev server is actually running, that the port in the mapping matches what it printed, and that the agent is installed on that same machine. If the agent is on a different computer — a Raspberry Pi, say — then 127.0.0.1 is wrong: start the dev server on 0.0.0.0and put your machine's LAN address in the mapping instead.
"Agent is offline"
The tunnel is down, not the dev server. Open the agent's settings window or local page and confirm it says connected, and that its card under Agents reads Online.
The page loads but styles, scripts or API calls fail
Something in the app is hard-coded to http://localhost:…. The page is on HTTPS, so the browser blocks those as mixed content. Step 5 is the fix — give the app its public base URL.
Hot reload keeps reconnecting
The reload socket is usually still aimed at localhostand its original port. Point your dev server's client port at 443 — server.hmr.clientPort in Vite, WDS_SOCKET_PORT in Create React App.
A sign-in page appears instead of your app
That is the MikRouter access gate, so the mapping has Require access passwordticked. Enter your account access password — or untick it, which you must do if the caller is a webhook.
Where to next
No agent on your development machine yet? Install one — Windows, Mac or Linux — then come back to step 1.
For how mappings, agents and agent servers fit together generally, see the Agent Tunnel overview.