ngrok's free plan costs $0, needs no card for HTTPS endpoints, and keeps an endpoint online for as long as your machine runs it. In exchange it caps data transfer at 1 GB and HTTP requests at 20,000 a month, allows 3 online endpoints and one assigned dev domain, and shows browser visitors a warning page before your app (ngrok's free plan limits↗). Measured through a tunnel, a first visit to a fresh Vite dev server costs about 3.8 MB and a reload costs 16 requests, so a demo to new visitors runs into the transfer cap first and a developer reloading alone runs into the request cap first.
I build LocalCan, so weigh the comparison with that in mind. Prices and limits checked November 2026, and the tables link their sources. An AI assistant helped draft this post. The commands and numbers come from my own runs.
What are ngrok's free tier limits?
ngrok groups the free plan into monthly quotas, per-minute rate limits, and limits that apply at all times (Free Plan Limits↗):
| Limit | ngrok free plan | Kind |
|---|---|---|
| Data transfer | 1 GB a month | Monthly quota |
| HTTP requests | 20,000 a month, more from the $5 credit | Monthly quota |
| TCP connections | 5,000 a month | Monthly quota |
| Logs and events | 10,000 a month | Monthly quota |
| Webhook verifications | 500 a month | Monthly quota |
| OAuth or OIDC users (traffic identities) | 3 a month | Monthly quota |
| HTTP request rate | 4,000 a minute | Rate limit |
| TCP connection rate | 100 a minute | Rate limit |
| Online endpoints | Up to 3 | At all times |
| Concurrent agents | 3 | At all times |
| Users | 1 | At all times |
| Domains | 1 assigned dev domain | At all times |
| Traffic Policy rules | 5 per policy | At all times |
| Endpoint timeout | None | At all times |
The transfer quota is "Data transfer out (includes traffic to agents)" on ngrok's pricing page↗, so request bodies on their way to your machine count as well as responses on their way to visitors. The free plan also comes with a $5 one-time usage credit, which lasts a year and cannot be spent on data transfer out (the FAQ on the same page). ngrok's machine-readable pricing↗ says it can buy HTTP requests past the 20,000 at $2 per 100,000, so up to 250,000 more if nothing else draws on it. The table on ngrok's Pricing and Limits page leaves that option out.
The dev domain looks like your-assigned-name.ngrok-free.app and stays the same when you restart the agent. TCP endpoints are included, but ngrok asks for a credit or debit card before the first one (ERR_NGROK_8013 in ngrok's error reference↗). Raw TLS endpoints, custom domains and wildcard domains need a paid plan. ngrok custom domain: price, CNAME setup and alternatives covers what your own domain costs.
When do ngrok's free limits reset?
The monthly quotas reset once a month, and ngrok words it two ways. Its free plan limits page says they "reset at the start of each monthly billing cycle", and its Pricing and Limits↗ page says "Your usage refreshes on the first day of each month". The Usage page in the ngrok dashboard shows what is left. Rate limits clear within a minute. The endpoint limit counts endpoints online at the same time, so it never resets: you free a slot by stopping an endpoint.
What happens when you go over?
ngrok's error reference↗ has a message for the transfer and request caps. ERR_NGROK_725 reads "This ngrok account has reached its network bandwidth limit for the month." and ERR_NGROK_727 reads "This ngrok account has reached its HTTP requests limit for the month." Going over the per-minute request rate is ERR_NGROK_734, which "will reset within 1 minute". Past the request cap, the $5 credit buys more requests until it runs out. The credit cannot buy transfer, so past the transfer cap the options are waiting for the reset or a paid plan. The cheapest is Hobbyist at $10 a month, or $96 a year, with 5 GB, 100,000 requests and no warning page. Your own domain needs Pay-as-you-go, at $20 a month plus usage (ngrok pricing↗). Nothing on the pricing page or in ngrok's terms of service↗ ties commercial use to a paid plan, though ngrok pitches Pay-as-you-go as the plan "for teams shipping production traffic".
Does the free plan show a warning page?
Yes. Browser visitors to a free endpoint get a warning page (ngrok calls it an interstitial) that says the site is served by ngrok. Clicking Visit sets a cookie that hides it on that domain for 7 days. Requests from code skip it, and so does any request with an ngrok-skip-browser-warning header or a non-standard User-Agent (Removing the interstitial page↗). Every paid plan removes it. Is ngrok safe? covers what a free ngrok URL shows a visitor and how company networks spot tunnels.
How far do 1 GB and 20,000 requests go?
To turn the caps into page loads, I scaffolded two fresh apps, a Vite 8.3.8 React app and a Next.js 16.4.6 app, and opened each dev server through a LocalCan Public URL (also called a tunnel) with the JSON event stream on:
localcan http 5390 -H "Host: {{target_host}}" --json > vite-tunnel.jsonl
Every request line in that stream carries the response size (response_bytes, see scripting quick tunnels with --json). Headless Chromium opened each page with an empty cache and then reloaded it, three runs per app, and the three runs matched within 4 bytes. The totals below are response bodies only. Headers and TLS overhead are not in them, so ngrok's meter may read higher for the same traffic.
| Page | Requests | Response bytes | Loads per 1 GB | Loads per 20,000 requests |
|---|---|---|---|---|
| Vite dev server, first visit | 21 | 3,761,358 | About 270 | About 950 |
| Vite dev server, reload | 16 | 0 | No body bytes | 1,250 |
| Next.js dev server, first visit | 24 | 871,553 | About 1,150 | About 830 |
| Next.js dev server, reload | 22 | 4,455 | About 224,000 | About 910 |
| Vite production build, first visit | 7 | 89,267 | About 11,200 | About 2,860 |
| Vite production build, reload | 7 | 0 | No body bytes | About 2,860 |
"Loads per 1 GB" counts 1 GB as 1,000,000,000 bytes. The Next.js rows were measured with allowedDevOrigins set, which the next section explains.
First visits spend transfer. In the Vite dev page, react-dom's development build is 3,132,085 of the 3,761,358 bytes, and Vite's dev server sends it uncompressed. Each person who opens a Vite dev-server link for the first time costs about 3.8 MB, so a demo to a group of new visitors reaches 1 GB after roughly 270 of them. A production build of the same page (vite build, then vite preview) came to 89,267 bytes, 42 times less. The Next.js dev server compresses its responses, so its first visit is lighter at 871,553 bytes, and there the request cap runs out first, after about 830 first visits.
Reloads spend requests. Once the browser has cached the dependencies, a reload asks the dev server about every source module and gets a 304 with no body for each. That sends no body bytes and costs 16 requests, so a developer reloading alone runs into the 20,000-request cap first, after about 1,250 reloads in a month.
Webhooks spend requests too. Each delivery, retries included, is one HTTP request, and its payload counts toward transfer on its way in to your machine. If nothing else uses the endpoint, 20,000 requests is about 670 deliveries a day over a 30-day month. Testing webhooks on a Public URL has a guide per provider.
Can an idle tab use up the request cap?
It can when hot reload is broken. With the default config, the Next.js 16 dev server refuses its dev resources to any origin it does not know, and its log said so during the first runs:
Cross-origin access to Next.js dev resources is blocked by default for safety.
To allow this host in development, add it to "allowedDevOrigins" in next.config.js and restart the dev server:
Behind a tunnel, that guard blocks the hot reload (HMR) websocket, and the dev client keeps trying to reconnect. One tab left open and untouched for 10 minutes made 243 requests: 96 failed websocket attempts, plus 7 full page reloads of 21 requests each that the dev client triggered by itself. That is about 1,460 requests an hour, so one forgotten tab would spend 20,000 requests in about 14 hours.
The fix is one line in next.config.ts, a hostname pattern for your tunnel (allowedDevOrigins↗):
const nextConfig: NextConfig = {
allowedDevOrigins: ['*.localcan.dev'],
}
On ngrok's free plan the entry is your dev domain (your-assigned-name.ngrok-free.app), and LocalCan's Free plan URLs need '*.trylocalcan.com'. After the change and a restart, an idle Next.js tab made 0 requests in 120 seconds.
Vite needed no config change because the tunnel rewrote the Host header to localhost (the -H flag above), and an idle Vite tab also stayed at 0. Without that rewrite Vite refuses the request because the host is not on its allowed list. Invalid Host header behind a tunnel has the fix for Vite, Next.js and other frameworks.
How do the ngrok, Pinggy and LocalCan free plans compare?
| ngrok free plan | Pinggy free plan | LocalCan Free plan | |
|---|---|---|---|
| Price | $0 (pricing↗) | $0 (pricing↗) | $0 (pricing↗) |
| Data transfer | 1 GB a month (pricing↗) | No cap (pricing↗) | 1 GB a calendar month (Free plan) |
| HTTP requests | 20,000 a month, more from the $5 credit, 4,000 a minute (limits↗, pricing↗) | None listed (pricing↗) | No cap (Free plan) |
| Session length | No timeout (limits↗) | 60 minutes (pricing↗) | 60 minutes (Free plan) |
| URL | 1 dev domain, kept across restarts (limits↗) | Random subdomain (pricing↗) | New random URL each session (Free plan) |
| URLs online at once | 1 dev domain, up to 3 endpoints on it (limits↗) | Not listed (pricing↗) | 1 (Free plan) |
| Browser warning page | Yes, hidden for 7 days after Visit (limits↗) | Yes, once per browser (screening page↗) | None (Free plan, tested October 2026) |
| TCP | Yes, after card verification (errors↗) | TCP, UDP and TLS (pricing↗) | Paid plans only (Free plan) |
| First paid plan | Hobbyist, $10 a month or $96 a year: 5 GB, 100,000 requests, no warning page (pricing↗) | Pro, $3 a month per seat or $2.50 a month yearly: persistent tunnels (pricing↗), no screening page (screening page↗) | Solo, $10 a month or $96 a year: no fixed transfer or request cap (fair use), 5 live Public URLs, TCP, 2 custom domains (pricing↗, terms↗, comparison) |
Each free plan wins somewhere. ngrok's is the only one of the three without a session limit, and its dev domain stays put, which matters for a webhook URL you paste into a provider's dashboard once and forget. Pinggy lists no transfer cap on its free plan, includes TCP, UDP and TLS tunnels, and Pinggy Pro is the cheapest way off a free plan. LocalCan's Free plan has the same 1 GB as ngrok and a 60-minute session like Pinggy, but no request cap and no page in front of your app, so the reloads and idle-tab retries measured above count only against its 1 GB. That makes it the better fit for showing a dev server to someone for an hour, and the weaker fit for a webhook endpoint that has to keep one address for weeks. Its first paid plan, Solo, costs the same as ngrok's Hobbyist and sets no fixed transfer or request cap. Six ngrok alternatives compared on free tiers and prices covers Cloudflare Tunnel, zrok, localtunnel and the rest.
How do you check what your own app costs?
Open the browser's developer tools, tick Disable cache in the Network tab, and load the page through the tunnel: the status bar shows the request count and the bytes transferred for a first visit (headers included, so a little above the body-only table). Untick it and reload for the reload cost.
To measure from the tunnel side, save the LocalCan event stream while you click through the app, stop it with Ctrl+C, and add it up with jq:
localcan http 5391 -H "Host: {{target_host}}" --json > visit.jsonl
jq -s '[.[] | select(.event=="request")] | {requests: length, response_bytes: (map(.response_bytes) | add)}' visit.jsonl
For one first visit to the Vite production build it printed:
{
"requests": 7,
"response_bytes": 89267
}
