LocalTunnel: A Free Alternative to Ngrok for Exposing Local Services

TL;DR: How to use LocalTunnel to expose local development servers to the internet without Ngrok's limitations

Overview

LocalTunnel is a free alternative to Ngrok for exposing local development servers to the internet. It’s particularly useful for testing webhooks, sharing work in progress, and mobile app development.

Why LocalTunnel?

Advantages

  1. No Tunnel Expiry - Unlike Ngrok’s free tier, LocalTunnel keeps the tunnel alive as long as your machine runs
  2. Automatic Reconnection - If the underlying server restarts, LocalTunnel reconnects without changing the exposed URL
  3. Free - No paid tier required for basic functionality

Disadvantages

  • No graphical UI for inspecting requests/responses
  • Request logging must be handled by your application

Installation

LocalTunnel requires Node.js. Install it globally:

npm install -g localtunnel

Basic Usage

Start Your Local Server

First, start your local application on a port (e.g., 8000):

# Example: Python server
python -m http.server 8000

# Example: Node.js server
node server.js  # assuming it runs on port 3000

Create the Tunnel

# Expose port 3000
lt --port 3000

# You may need sudo on some systems
sudo lt --port 3000

This generates a public URL like https://flkajsfljas.loca.lt.

Advanced Options

Custom Subdomain

# Request a specific subdomain (may not always be available)
lt --port 3000 --subdomain myapp

# Result: https://myapp.loca.lt

Keep Alive Connection

# For more reliable connections on flaky networks
lt --port 3000 --keepalive

Local Host Binding

# If your server binds to a specific host
lt --port 3000 --local-host 127.0.0.1

Architecture

┌──────────────────────────────────────────────────────────────────────┐
│                         Internet                                     │
│                                                                      │
│    External Request ─────► https://myapp.loca.lt                    │
└───────────────────────────────┬──────────────────────────────────────┘


┌──────────────────────────────────────────────────────────────────────┐
│                    LocalTunnel Server                                │
│              (Managed service at loca.lt)                           │
└───────────────────────────────┬──────────────────────────────────────┘

                                │  WebSocket Connection
                                │  (Initiated by your machine)

┌──────────────────────────────────────────────────────────────────────┐
│                    Your Development Machine                          │
│                                                                      │
│    ┌────────────────┐      ┌────────────────────────┐               │
│    │  lt (tunnel)   │ ───► │  localhost:3000        │               │
│    │                │      │  (Your Application)     │               │
│    └────────────────┘      └────────────────────────┘               │
└──────────────────────────────────────────────────────────────────────┘

Common Use Cases

1. Webhook Development

# Start your webhook handler
node webhook-handler.js  # listens on 4000

# Expose it
lt --port 4000 --subdomain mywebhook

# Configure third-party service to send webhooks to:
# https://mywebhook.loca.lt/webhook/endpoint

2. Mobile App Development

# Start your API server
./gradlew bootRun  # Spring Boot on 8080

# Expose for mobile testing
lt --port 8080 --subdomain myapi

# Configure mobile app to use:
# https://myapi.loca.lt as the API base URL

3. Demo Sharing

# Start frontend development server
npm run dev  # Vite on 5173

# Share with stakeholders
lt --port 5173 --subdomain demo

# Share URL: https://demo.loca.lt

Running as a Background Service

# Install pm2
npm install -g pm2

# Start tunnel as background process
pm2 start lt -- --port 3000 --subdomain myapp

# Check status
pm2 status

# View logs
pm2 logs lt

# Auto-start on system reboot
pm2 startup
pm2 save

Using nohup

# Start in background
nohup lt --port 3000 > tunnel.log 2>&1 &

# Check if running
ps aux | grep lt

Troubleshooting

SSL Certificate Errors

If you see SSL expiry errors, try using HTTP:

# Change from https://flkajsfljas.loca.lt
# to      http://flkajsfljas.loca.lt

Connection Refused

Ensure your local server is running and accessible:

# Test local connectivity
curl http://localhost:3000

# Then start the tunnel
lt --port 3000

Tunnel Disconnects

Use the --keep-alive option or wrap with a restart script:

#!/bin/bash
# auto-restart-tunnel.sh

while true; do
  lt --port 3000 --subdomain myapp
  echo "Tunnel disconnected, restarting in 5 seconds..."
  sleep 5
done

Comparison: LocalTunnel vs Ngrok

FeatureLocalTunnelNgrok (Free)
PriceFreeFree (limited)
Tunnel DurationUnlimited2 hours
Request Inspector
Custom Subdomain❌ (paid only)
Reconnect URLSame URLNew URL
HTTPS
TCP Tunneling

Best Practices

  1. Use custom subdomains for consistent URLs across sessions
  2. Implement your own logging since there’s no built-in inspector
  3. Add authentication to your exposed services for security
  4. Don’t expose production databases or sensitive services
  5. Use environment variables to switch between tunnel and production URLs