2026-05-16DEVELOPMENT

How to Configure a Telegram Mini App for Development Across Multiple Ports

BotFather won't accept your localhost URL. You've spent hours trying beta versions, test servers, and dev mode workarounds. Here's the setup that actually works — a local reverse proxy through Caddy, tunneled to the internet through ngrok.


What Are Telegram Bots and Mini Apps?

If you're already familiar, skip ahead. If not, here's the short version.

Bots are programs that interact with Telegram's API on your behalf. They can respond to messages, send notifications, manage groups, and more. They communicate with Telegram either through webhooks (Telegram pushes updates to your server) or polling (your server repeatedly asks Telegram "anything new?").

Mini Apps are a newer Telegram feature — essentially full web applications that run inside the Telegram client. From the user's perspective they feel native; under the hood they're just websites rendered in a WebView. You register them through BotFather and they launch from a bot's menu button or inline keyboard.


The Problem

Here's where development gets painful.

When you register a Mini App URL in BotFather, it only accepts https:// URLs with a valid SSL certificate, This is a security requirement on Telegram's end — they don't want Mini Apps served over unencrypted connections. Reasonable in production. A headache in development.

The real problem compounds when you have a monorepo or multi-service setup. A typical project might look like this:

| Service | Port | | -------------- | ------------------------ | | API / Backend | 3000 | | Web (Mini App) | 5174 | | Admin Panel | 5175 | | Telegram Bot | polling — no port needed |

Your Mini App lives on 5174. Your frontend calls the API on 3000. But to share a single HTTPS URL with BotFather, you need one port exposed to the internet — not three.


What I Tried First

Before landing on this solution, I went through the usual rabbit holes:

  • Telegram Beta — installed the beta client hoping for relaxed URL validation. No luck.
  • Test servers — Telegram provides a test environment accessible via @BotFather on test.telegram.org. It has quirks and the login flow is non-obvious. I couldn't get it working reliably.
  • Dev mode workarounds — there are some flags and workarounds floating around online. None of them consistently bypassed the HTTPS requirement for Mini App URLs.

If you've been down the same paths, you're not missing something obvious. The requirement is enforced.


The Insight

The fix is simple in hindsight: funnel all your ports through one port with a reverse proxy, then expose that single port to the internet.

Instead of dealing with three separate ports, you create a single gateway — one port that routes traffic to the right service based on the URL path. Then you point ngrok at that one port and get a single HTTPS URL that covers everything.

That's the whole idea. The rest is just implementation.


The Tools

Caddy

Caddy is a modern web server and reverse proxy written in Go. What makes it great for local development is its simplicity — configuration is a single human-readable file (Caddyfile) with almost no boilerplate. No XML, no YAML sprawl, no nginx syntax puzzles.

For our purposes, Caddy will sit in front of all three services and route incoming requests by path:

  • /api/* → forward to your backend on port 3000
  • /admin/* → forward to your admin panel on port 5175
  • everything else → forward to your Mini App on port 5174

ngrok

ngrok creates a secure tunnel from a public URL to a port on your local machine. When you run ngrok http 8080, it gives you something like:

https://throat-precook-resistant.ngrok-free.app → http://localhost:8080

That public URL has a valid SSL certificate, which means Telegram accepts it. The free tier gives you one tunnel and a randomly generated subdomain — good enough for development.


The Setup

Step 1 — Install the tools

Caddy:

# macOS
brew install caddy

# Ubuntu / Debian
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update && sudo apt install caddy

# Windows (via Chocolatey)
choco install caddy

ngrok:

Download from ngrok.com/download or:

# macOS
brew install ngrok/ngrok/ngrok

# Snap (Linux)
snap install ngrok

Sign up for a free account and authenticate:

ngrok config add-authtoken YOUR_AUTH_TOKEN

Step 2 — Create your Caddyfile

In the root of your project, create a file named Caddyfile (no extension):

:8080 {
    handle /api* {
        uri strip_prefix /api
        reverse_proxy localhost:3000
    }

    handle /admin* {
        uri strip_prefix /admin
        reverse_proxy localhost:5175
    }

    handle {
        reverse_proxy localhost:5174
    }
}

What's happening here:

  • :8080 — Caddy listens on port 8080
  • handle /api* — any request starting with /api gets the /api prefix stripped and forwarded to your backend on port 3000. So https://your-ngrok-url.ngrok-free.app/api/users becomes http://localhost:3000/users
  • handle /admin* — same pattern for your admin panel on 5175
  • handle (no path) — the catch-all; everything else goes to your Mini App on 5174
  • uri strip_prefix — this is important. Without it, your backend would receive /api/users instead of /users, which likely won't match your routes

Step 3 — Start your services

Make sure all three services are running on their respective ports before starting Caddy. How you do this depends on your stack — npm run dev, go run ., whatever applies.


Step 4 — Run Caddy

From the directory containing your Caddyfile:

caddy run

Or if you want it in the background:

caddy start

Caddy will log that it's listening on port 8080. You can verify it's routing correctly by visiting http://localhost:8080, http://localhost:8080/api, and http://localhost:8080/admin in your browser.


Step 5 — Open the ngrok tunnel

ngrok http 8080

You'll see output like this:

Forwarding  https://throat-precook-resistant.ngrok-free.app -> http://localhost:8080

Your three services are now reachable at:

https://throat-precook-resistant.ngrok-free.app           → Mini App (5174)
https://throat-precook-resistant.ngrok-free.app/api       → Backend API (3000)
https://throat-precook-resistant.ngrok-free.app/admin     → Admin Panel (5175)

Step 6 — Register the URL with BotFather

Open Telegram, find @BotFather, and update your Mini App URL:

/mybots → select your bot → Bot Settings → Menu Button → Edit Menu Button URL

Paste your ngrok HTTPS URL. BotFather accepts it. You're done.

Bonus: Telegram Dev Mode and Web Inspection

Telegram has a hidden developer mode that enables right-click inspection inside Mini Apps — invaluable for debugging network requests and console logs without printf-driven development.

To activate it:

On Android or Windows: Go to your Telegram profile → tap the version number 10 times rapidly.
On macOS: Same, but 5 taps.

Once activated, go to Settings → Advanced → Experimental and toggle on Enable WebView Inspection (the exact label varies by platform).

Now open any Mini App, right-click anywhere inside it, and you'll see an Inspect option. This opens a full browser DevTools panel — network tab, console, sources, everything.

This alone saves significant debugging time when something isn't working right in the Mini App context.


Wrapping Up

The core constraint — Telegram requires HTTPS for Mini App URLs — isn't going away, but working around it locally is straightforward once you know the pattern:

  1. Route all services through one port with Caddy
  2. Expose that port to the internet with ngrok
  3. Hand the resulting HTTPS URL to BotFather

It's a setup you configure once per project and forget about. Hope it saves you the hours of trial and error it took me.


If you found this useful or hit a snag with your setup, feel free to reach out. Happy building. ❤️