Expose Multiple Local Services with Reserved ngrok Subdomains from One Config File
Define multiple named HTTP tunnels in ngrok.yml with stable subdomains or custom domains, start them with one command, and verify each endpoint with curl and the local web inspector.
26 Dec 2025, 07:29 UTC

Why ad-hoc ngrok sessions break down with multiple services
Running a separate ngrok http command for each local service produces random URLs, forces you to manage multiple terminal windows, and makes it easy to lose track of which port maps to which public endpoint. A single configuration file removes that friction: you describe each tunnel once, assign a stable identifier, and start every session together.
Desired outcome
By the end of this guide you will have an ngrok.yml file that defines two or more HTTP tunnels, each pointing to a different local port and exposing either a reserved ngrok.io subdomain or a custom domain you own. You will start them with one command and confirm that each public URL reaches the correct local service.
Prerequisites and plan limits
- ngrok CLI installed on the machine that runs your local services.
- An authtoken from the ngrok dashboard. This links the agent to your account.
- A paid plan if you want to reserve a specific
subdomainunderngrok.io. Free accounts receive randomly generated subdomains and cannot request a custom one. - Local services already listening on the ports you plan to expose (for example, a development API on port 3000 and a web front end on port 8080).
- DNS management access if you intend to use a custom domain rather than an
ngrok.iosubdomain.
Store the authentication token
ngrok needs an authtoken to reserve subdomains and to associate traffic with your account. You can provide it in two ways:
- Set the
NGROK_AUTHTOKENenvironment variable before starting the agent. - Place an
authtokenkey directly in yourngrok.ymlfile.
Both methods work, but the config file stores the token in plain text on disk. If the host is shared or compromised, anyone with file access can use your account. For production or CI environments, prefer a secrets manager or environment injection, and restrict file permissions on the config itself.
Define multiple tunnels in ngrok.yml
The tunnels section is a map of named entries. Each entry needs at minimum a proto (the protocol, usually http) and an addr (the local address and port). Add a subdomain key to request a stable ngrok.io hostname.
Create or edit your config file (commonly ngrok.yml in your home directory, or pass an explicit path with --config):
authtoken: <your-ngrok-authtoken>
tunnels:
billing-api:
proto: http
addr: 3000
subdomain: acme-billing
storefront:
proto: http
addr: 8080
subdomain: acme-store
In this example, billing-api and storefront are arbitrary labels you choose. The subdomain values must be unique across all ngrok users; if acme-billing is already taken, the agent will fail to start that tunnel.
Using a custom domain instead
If you own a domain, replace subdomain with custom_domain:
tunnels:
marketing-site:
proto: http
addr: 4000
custom_domain: app.example.com
Before starting the tunnel, create a CNAME DNS record for app.example.com that points to the ngrok hostname provided for your account (for example, the ngrok.io address shown in your dashboard). SSL certificates for custom domains are typically managed through the ngrok dashboard; you may need to wait for certificate issuance after the DNS record propagates.
Start all tunnels at once
Run the following command in a terminal on the host where your local services run. No elevated privileges are required:
ngrok start --all
The agent reads the config file, opens a session for every tunnel listed, and prints the public URL for each one. You should see output similar to:
billing-api https://acme-billing.ngrok.io -> http://localhost:3000
storefront https://acme-store.ngrok.io -> http://localhost:8080
Note: exact formatting varies by ngrok version. The key check is that each named tunnel appears with the subdomain or custom domain you requested.
Verify that traffic reaches the right service
Use curl from any machine with internet access, substituting the subdomain or custom domain from your config:
curl -I http://acme-billing.ngrok.io
A successful response returns HTTP headers from your local service, not from ngrok. If you receive a connection error, confirm the local service is listening and that the addr port matches.
For deeper inspection, open the ngrok web interface in a browser on the same machine:
http://127.0.0.1:4040/tunnels
This page lists every active tunnel, its public URL, and its target address. It also captures request and response details, which is useful when a tunnel starts but the application returns unexpected errors.
Recovery options when a tunnel fails
| Symptom | Likely cause | What to do |
|---|---|---|
| Agent exits with a subdomain error | Free plan, or subdomain already reserved by another account | Upgrade to a paid plan or choose a different subdomain value |
| Custom domain returns certificate warnings | CNAME not fully propagated, or SSL not yet issued | Verify DNS resolution, then re-check the ngrok dashboard for certificate status |
connection refused in the inspector | Local service is down or addr points to the wrong port | Restart the local service and confirm the port with a local request first |
| Token rejected at startup | Invalid or revoked authtoken | Copy a fresh token from the dashboard and update the config or environment variable |
Limitations and current verification
Reserved subdomains are a paid feature; attempting to use the subdomain key on a free account will prevent that tunnel from starting. DNS changes for custom domains can take several minutes to propagate, so a newly created CNAME may not resolve immediately. Because ngrok's plan features and configuration schema can change, verify the current requirements for subdomain reservation, custom domain SSL, and config file syntax against the official ngrok documentation before relying on this setup in production.
A quick way to confirm the final state
After starting the tunnels, check three things: the console shows one URL per named tunnel, curl against each public URL returns content from the expected local service, and the inspector at 127.0.0.1:4040 lists both tunnels as active. If all three pass, your multi-tunnel configuration is working.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.