Saltar al contenido principal

ngrok

El túnel local más popular del ecosistema. Su mayor ventaja es la interfaz web de inspección de requests, que te permite ver y repetir cada petición HTTP recibida — invaluable para depurar webhooks.

Límites del plan gratuito

ParámetroValor
CostoGratis con cuenta
Túneles simultáneos1
HTTPS✅ Automático
URLAleatoria en cada sesión
URL estática✅ 1 dominio estático gratuito (desde 2024)
Inspector web✅ localhost:4040
Replay de requests✅ (muy útil para webhooks)
Requests/min40 (free)

Instalación

# Mac:
brew install ngrok/ngrok/ngrok

# Windows (con winget):
winget install ngrok

# O descargar desde: https://ngrok.com/download
# (un solo binario, sin instalación)

# Linux:
curl -s https://ngrok-agent.s3.amazonaws.com/ngrok.asc \
| sudo tee /etc/apt/trusted.gpg.d/ngrok.asc >/dev/null \
&& echo "deb https://ngrok-agent.s3.amazonaws.com buster main" \
| sudo tee /etc/apt/sources.list.d/ngrok.list \
&& sudo apt update && sudo apt install ngrok

# Autenticar (crear cuenta gratis en ngrok.com):
ngrok config add-authtoken TU_TOKEN
# El token está en: https://dashboard.ngrok.com/get-started/your-authtoken

Uso básico

# Tu app corre en localhost:3000
ngrok http 3000

# Puerto diferente
ngrok http 8080

# Con host header (para frameworks que necesitan el host correcto)
ngrok http --host-header=rewrite 3000

# Salida:
# ngrok
#
# Session Status online
# Account tu@email.com (Plan: Free)
# Version 3.x.x
# Region United States (us)
# Web Interface http://127.0.0.1:4040 ← Interfaz de inspección
# Forwarding https://abc123.ngrok-free.app -> http://localhost:3000
#
# Connections ttl opn rt1 rt5 p50 p90
# 0 0 0.00 0.00 0.00 0.00

La killer feature: el Inspector Web

Mientras ngrok está corriendo, abre http://localhost:4040 en tu browser:

┌─────────────────────────────────────────────────────────┐
│ ngrok Inspector localhost:4040 │
├─────────────────────────────────────────────────────────┤
│ │
│ Requests │
│ ──────── │
│ POST /webhook/stripe 200 OK 2ms ▼ │
│ ├─ Headers │
│ │ stripe-signature: t=1234... │
│ │ content-type: application/json │
│ │ │
│ ├─ Body (Request) │
│ │ { │
│ │ "type": "payment_intent.succeeded", │
│ │ "data": { "object": { "amount": 2000, ... } } │
│ │ } │
│ │ │
│ └─ Body (Response) │
│ { "received": true } │
│ │
│ [Replay ▶] ← Reenviar esta petición sin ir a Stripe │
└─────────────────────────────────────────────────────────┘

El botón Replay es fundamental: en vez de tener que ir a Stripe y disparar un evento de prueba cada vez, simplemente repites la petición anterior. Ahorra muchísimo tiempo al depurar webhooks.


Dominio estático gratuito

Desde 2024, ngrok ofrece 1 dominio estático gratuito (la URL no cambia entre sesiones):

# Ver tu dominio estático asignado:
# ngrok.com → Dashboard → Domains

# Usarlo:
ngrok http --domain=tu-nombre-asignado.ngrok-free.app 3000

# Ejemplo:
ngrok http --domain=adorable-puppy-honestly.ngrok-free.app 3000

Esto es muy útil para configurar webhooks y OAuth callbacks una sola vez sin tener que actualizar la URL cada vez que reinicias ngrok.


Archivo de configuración

# ~/.config/ngrok/ngrok.yml  (o ngrok.yml en el directorio actual)
version: "2"
authtoken: TU_AUTHTOKEN

tunnels:
# Túnel principal
web:
proto: http
addr: 3000
# domain: tu-dominio.ngrok-free.app # Si tienes dominio estático

# Túnel para la API (en el plan pagado se pueden tener múltiples)
api:
proto: http
addr: 8080
# Iniciar túnel del archivo de configuración:
ngrok start web

# Iniciar todos los túneles del archivo:
ngrok start --all

Integración con frameworks populares

Next.js

# Next.js dev server corre en 3000 por defecto
npm run dev &
ngrok http 3000
// next.config.ts - permitir el dominio de ngrok para imágenes
import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: '*.ngrok-free.app', // Para imágenes externas
},
],
},
async headers() {
return [
{
source: '/(.*)',
headers: [
{
key: 'ngrok-skip-browser-warning',
value: '1', // Evita la página de warning de ngrok en desarrollo
},
],
},
]
},
}

export default nextConfig

Vite (React, Vue, Svelte)

npm run dev &   # → localhost:5173
ngrok http 5173 --host-header="localhost:5173"

FastAPI / Django

uvicorn main:app --reload &   # → localhost:8000
ngrok http 8000

Caso de uso: Depurar webhook de GitHub

# 1. Crear un repositorio de prueba en GitHub
# 2. Configurar webhook:
# Repo → Settings → Webhooks → Add webhook
# Payload URL: https://abc123.ngrok-free.app/webhook/github
# Content type: application/json
# Events: Push events, Pull request events

# 3. Levantar tu servidor local + ngrok
node server.js &
ngrok http 3000

# 4. En tu servidor:
import crypto from 'crypto'
import express from 'express'

const app = express()

app.post('/webhook/github',
express.raw({ type: 'application/json' }),
(req, res) => {
// Verificar firma de GitHub
const signature = req.headers['x-hub-signature-256'] as string
const secret = process.env.GITHUB_WEBHOOK_SECRET!
const hmac = crypto.createHmac('sha256', secret)
hmac.update(req.body)
const expected = `sha256=${hmac.digest('hex')}`

if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
return res.status(401).send('Firma inválida')
}

const event = req.headers['x-github-event']
const payload = JSON.parse(req.body.toString())

console.log(`Evento: ${event}`)

if (event === 'push') {
const branch = payload.ref.replace('refs/heads/', '')
console.log(`Push a rama: ${branch}`)
console.log(`Commits: ${payload.commits.length}`)
// → Disparar re-deploy, notificar Slack, etc.
}

if (event === 'pull_request') {
console.log(`PR #${payload.number}: ${payload.action}`)
// → Correr tests, asignar reviewers, etc.
}

res.json({ ok: true })
}
)

Luego en ngrok Inspector (localhost:4040) puedes:

  1. Ver cada petición que llegó con sus headers y body
  2. Hacer clic en Replay para reenviar el mismo evento sin ir a GitHub
  3. Modificar el body antes de reenviar (en el plan pagado)

Usar ngrok desde Node.js (programático)

Puedes iniciar ngrok directamente desde tu código:

npm install ngrok
import ngrok from 'ngrok'

async function startServer() {
// Iniciar tu servidor
const PORT = 3000
app.listen(PORT)
console.log(`Servidor en puerto ${PORT}`)

// Abrir túnel automáticamente
if (process.env.NODE_ENV === 'development') {
const url = await ngrok.connect({
addr: PORT,
authtoken: process.env.NGROK_AUTHTOKEN,
})
console.log(`🌐 Túnel ngrok: ${url}`)
console.log(`🔍 Inspector: http://localhost:4040`)
}
}

startServer()

⚠️ Limitaciones importantes

  • 1 solo túnel activo a la vez
  • 40 requests/minuto: si recibes más, ngrok los rechaza con 429
  • La URL cambia en cada reinicio (excepto el dominio estático)
  • Sin autenticación básica en URLs (pago)
  • Sin IP whitelisting (pago)

Comparación ngrok vs Cloudflare Tunnel

ngrokCloudflare Tunnel
Inspector de requests✅ Excelente❌ No tiene
Replay de webhooks
Velocidad⚡ Buena⚡⚡ Mejor
Sin registro❌ Requiere cuenta✅ Modo rápido
Requests/min40 (free)Sin límite
URL fija gratis✅ 1 dominioCon cuenta
Múltiples túneles❌ 1 (free)✅ Varios

Conclusión:

  • Debuggear webhooks → ngrok (por el inspector)
  • Demos, pruebas en móvil, uso general → Cloudflare Tunnel