The Laravel & PHP Ecosystem: Expose vs. ngrok

 IT

InstaTunnel Team
Published by the InstaTunnel team | Editorial policy
The Laravel & PHP Ecosystem: Expose vs. ngrok

Current comparison

Looking for the main ngrok alternative guide?

We keep the latest ngrok alternative comparison, CLI commands, pricing notes, and webhook examples on one canonical page.

Open the InstaTunnel ngrok alternative guide

Quick answer

Expose vs ngrok: Self-Hosting a PHP Reverse Proxy for Larave: quick answer

If free tunnel limits interrupt your workflow, compare session length, stable URLs, concurrent tunnels, and paid-plan pricing before choosing a localhost tunnel tool.

What free tunnel limits should developers check first?

Check session duration, URL stability, concurrent tunnels, custom subdomains, bandwidth or request limits, and whether webhook callbacks survive restarts.

How does InstaTunnel handle longer development sessions?

InstaTunnel Free is designed around 24-hour sessions, with Pro available for higher limits and MCP endpoint tunnel workflows.

As a modern PHP or Laravel developer, local development is a well-oiled machine. You likely rely on tools like Laravel Herd, Valet, or Sail to serve your applications on a .test domain. But what happens when you need to share that local environment with the outside world — demoing a work-in-progress feature to a client, testing webhooks from Stripe or GitHub, or previewing a responsive layout on a real phone? You need a tunnel.

For over a decade, the default answer has been ngrok. It’s robust and works with any stack, but its free tier comes with trade-offs that can disrupt a workflow. One alternative worth knowing about is Expose, an open-source, PHP-native tunneling tool built by Marcel Pociot at Beyond Code — the team behind Laravel Herd and Tinkerwell.

This guide compares the two tools as they actually work today, then walks through self-hosting your own Expose server on a budget VPS.


The ngrok free tier, accurately described

ngrok has grown well past a simple tunneling utility into a full ingress platform. Its free plan is more generous than a lot of comparison content suggests, but it does have real limits, and a couple of commonly repeated claims about it don’t hold up.

What’s actually capped on the free plan, per ngrok’s current documentation:

ResourceFree tier limit
Data transfer out1 GB/month
HTTP/S requests20,000/month
Online endpointsUp to 3
Concurrent agents3
TCP connections5,000/month
HTTP request rate4,000/minute
TCP connection rate100/minute
Custom/branded domainsNot available on Free (Hobbyist adds ngrok-branded domains; bring-your-own-domain requires Pay-as-you-go)

Two points worth being precise about, since they’re the most common source of confusion:

  • There’s no session timeout on the free tier. ngrok’s docs state this explicitly — free endpoints can run indefinitely as a background service. There’s no “restart every few hours” requirement.
  • You don’t have to get a new random subdomain every time. Since 2023, every ngrok account — including free ones — can claim one free static domain (something like panda-new-kit.ngrok-free.app) that stays yours permanently and doesn’t change on restart. It’s not a fully custom/branded domain (that’s still paid), but it does solve the “I have to update my Stripe webhook URL every time I restart the tunnel” problem, as long as you’ve claimed it. Without claiming a static domain, you do still get a fresh random one on each connection.
  • Free-tier HTTP/S traffic does get an interstitial “click through to continue” warning page in the browser, mainly to deter phishing abuse. It doesn’t affect programmatic requests (like an actual webhook POST from Stripe), and it can be bypassed even on the free tier by sending a custom User-Agent or the ngrok-skip-browser-warning header.

Above Free, current plans are Hobbyist ($8–10/month depending on annual vs. monthly billing, ngrok-branded domains, no interstitial), Pay-as-you-go ($20/month base plus metered usage, unlimited endpoints, bring-your-own-domain), and Enterprise (custom pricing, SSO/SCIM/compliance).


Expose: a PHP-native, open-source alternative

Expose shares HTTP/HTTPS traffic only — it’s not a generic TCP/UDP transport the way ngrok is, so it won’t tunnel a raw database connection or an SSH session. In exchange, it’s scoped tightly to the web development workflow and its core is fully open source (MIT-licensed) and written in PHP, so if something breaks, you can actually read the code that’s running.

As of this writing the project is active: the exposedev/expose repository has around 4,500+ stars and 300+ forks on GitHub, with its latest release (3.2.2) shipped in March 2026. The current client requires PHP 8.2 or newer — this is a hard requirement in the package’s composer.json, not just a recommendation, so older “PHP 7.4+” guidance floating around online is out of date.

Installing the client

Three supported methods:

Laravel Herd — if you already use Herd, Expose ships with it; just set your token in Herd’s settings.

PHP Archive (PHAR) — the primary documented method:

curl https://github.com/exposedev/expose/raw/master/builds/expose -L --output expose
chmod +x expose
sudo mv expose /usr/local/bin/expose

Via Composer:

composer global require exposedev/expose

(Note: the package was historically published as beyondcode/expose; that name still resolves on Packagist, but current docs use exposedev/expose.) Make sure your global Composer bin directory is on your PATH.

Sharing a site

expose token YOUR_TOKEN

Then, from a .test project directory (Herd/Valet convention), just run expose with no arguments, or share an explicit URL:

expose share http://localhost:8000

A local request dashboard at http://127.0.0.1:4040 lets you inspect and replay incoming requests, similar in spirit to ngrok’s inspector.

One correction to a common claim: custom subdomains are not available on Expose’s free hosted tier. Passing --subdomain requires either an Expose Pro/Team account or your own self-hosted server:

expose share my-site.test --subdomain=my-site

On the free sharedwithexpose.com server (hosted by Beyond Code in Germany), every connection gets a random subdomain and connections are time-limited. This is exactly the pain point that makes self-hosting worth it if you want stable, custom URLs without paying per seat.

Current Expose pricing (expose.dev)

TierPriceIncludes
HobbyFreeTLS/SSL, time-limited connections, random URLs, single EU server
Pro$79/user/yearNo time limit, persistent URLs, custom domains and subdomains, global server network
Team$229/team/year (up to 10 users)Everything in Pro, plus priority support

Laravel Valet and Herd integration

Laravel Valet (Taylor Otwell’s separate, community-maintained local dev tool) supports three share tools as of current versions: ngrok, Expose, and Cloudflare’s cloudflared — selected via valet share-tool ngrok|expose|cloudflared. Valet will prompt to install whichever you pick (Homebrew for ngrok/cloudflared, Composer for Expose) and then valet share uses it.

Laravel Herd — Beyond Code’s own local environment — has Expose built in natively, configurable straight from Herd’s settings UI, with no separate install step.


Feature comparison

ngrok (Free/Hobbyist)Expose (Hobby/Pro)
Primary use caseGeneric network ingress; enterprise API/K8sWeb dev local environments, HTTP/S only
LanguageGo, closed-source client/serverPHP, open source (MIT)
ProtocolsHTTP/S, TCP, TLSHTTP/S only
Free-tier stable URLYes, if you claim your free static domainNo — random subdomain every connection
Custom subdomainsPaid onlyPaid (Pro) or free via self-hosting
Request inspectorYes (localhost:4040-style dashboard)Yes
Self-hostingNot applicable (managed only)Yes, fully documented and open source

Self-hosting Expose on a budget VPS

If you’re PHP/Laravel-centric and want stable custom subdomains without an annual per-seat fee, self-hosting is the real differentiator. Here’s the current, correct process — note that this differs in a few important ways from older guides (including earlier drafts of this piece): the self-hosted server is now a separate package from the client, with its own binary and its own repository.

Step 1: Provision the server and configure DNS

A basic DigitalOcean Droplet now starts at $4/month (512 MiB RAM, 1 shared vCPU, 10 GiB SSD, 500 GiB transfer) — plenty for a personal or small-team Expose server. Ubuntu 24.04 LTS is a solid choice.

Add two DNS A records pointing at your Droplet’s IP: - expose (or whatever subdomain you want as the base, e.g. expose.yourdomain.com) - *.expose (the wildcard, so every tunnel subdomain resolves)

Step 2: Install PHP and Composer

Expose’s server package requires PHP 8.2+:

sudo apt update
sudo apt install php-cli php-curl php-mbstring php-xml php-zip php-sqlite3 unzip git -y

curl -sS https://getcomposer.org/installer -o composer-setup.php
sudo php composer-setup.php --install-dir=/usr/local/bin --filename=composer

Step 3: Install the Expose server (not the client)

The self-hosted server lives in its own repository, exposedev/server, separate from the exposedev/expose client package. Clone it and install dependencies:

git clone https://github.com/exposedev/server.git
cd server
composer install

The open-source server core gives you the tunneling infrastructure itself. It does not include the hosted Expose Pro dashboard for team/billing management — that’s specific to the managed expose.dev platform.

Step 4: Start the server

php expose-server serve expose.yourdomain.com

This listens on port 8080 by default. You can change the port with --port=3000, and restrict connections to holders of a valid auth token with --validateAuthTokens (tokens are then issued/managed through the server’s admin interface).

Step 5: Put Nginx (or Apache) in front for TLS

The Expose server doesn’t terminate SSL itself — you need a reverse proxy. Obtain a wildcard certificate first (Certbot with a DNS challenge works well for *.expose.yourdomain.com), then configure Nginx:

server {
    listen        443 ssl;
    listen        [::]:443 ssl;
    server_name   expose.yourdomain.com *.expose.yourdomain.com;

    ssl_certificate      /etc/letsencrypt/live/expose.yourdomain.com/fullchain.pem;
    ssl_certificate_key  /etc/letsencrypt/live/expose.yourdomain.com/privkey.pem;

    location / {
        proxy_pass             http://127.0.0.1:8080;
        proxy_read_timeout     60;
        proxy_connect_timeout  60;
        proxy_redirect         off;

        # Required for WebSockets — Expose's tunnel connection depends on this
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto https;
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }
}
sudo ln -s /etc/nginx/sites-available/expose /etc/nginx/sites-enabled/
sudo systemctl restart nginx

(Apache and HAProxy configs following the same pattern are in Expose’s official docs if you’d rather not use Nginx.)

Step 6: Keep it running with Supervisor

sudo apt install supervisor -y
sudo nano /etc/supervisor/conf.d/expose.conf
[program:expose]
command=/usr/bin/php /home/expose/server/expose-server serve expose.yourdomain.com
numprocs=1
autostart=true
autorestart=true
user=expose
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start expose

(Alternative: Expose’s server repo ships a docker-compose.yaml. Copying .env-example to .env, setting PORT, DOMAIN, ADMIN_USERNAME, and ADMIN_PASSWORD, then running docker-compose up -d gets you the same result with less manual setup, at the cost of a bit less transparency into what’s running.)

Step 7: Point your local client at your server

On your local machine, with the Expose client installed:

expose default-server expose.yourdomain.com
expose token YOUR_TOKEN

Then share a site with a fixed subdomain — something you can’t do on the free public server:

expose share http://ecommerce.test --subdomain=checkout

Your app is now live at https://checkout.expose.yourdomain.com, on infrastructure you control, with no per-connection time limit and no per-seat fee.


Which one actually fits

Stick with ngrok if you need raw TCP/TLS tunneling (databases, SSH, game servers), Kubernetes ingress, org-level SSO, or compliance documentation — none of which Expose does.

Consider Expose if your traffic is HTTP/HTTPS only, you’re already in the Laravel/Herd ecosystem, and you’d rather run a $4/month VPS than pay a per-seat annual fee for stable custom subdomains — or you just want to be able to read the tunneling tool you depend on.


Changelog

Corrections and additions made to the original draft, checked against ngrok’s and Expose’s current documentation, GitHub/Packagist metadata, and DigitalOcean’s pricing page:

  1. ngrok session timeouts — Original draft claimed ngrok’s free tier imposes session timeouts requiring frequent restarts. ngrok’s documentation states free-tier endpoints have no timeout and can run indefinitely as a background service. Source: ngrok Free Plan Limits documentation.
  2. ngrok “random subdomain every restart” — Original draft treated this as unconditional. Since 2023, every ngrok account (including free) can claim one permanent static domain that doesn’t change on restart; only accounts that haven’t claimed one still get a new random subdomain each time. Source: ngrok’s “Static domains for all ngrok users” post; ngrok domains documentation.
  3. ngrok rate limit figure — Original draft cited a flat “40 connections per minute.” Current documented free-tier limits are 100 TCP connection attempts/minute and 4,000 HTTP requests/minute (separate figures, not one). Source: ngrok Free Plan Limits / Pricing and Limits documentation.
  4. DigitalOcean droplet price — Original draft used $5/month. DigitalOcean’s cheapest Basic Droplet is now $4/month (512 MiB RAM, 1 vCPU, 10 GiB SSD, 500 GiB transfer). Source: DigitalOcean Droplet pricing page.
  5. Composer package name — Original draft used composer global require beyondcode/expose. Current official installation docs specify exposedev/expose; beyondcode/expose still exists on Packagist as the legacy name pointing at the same project. Source: Expose Installation documentation; Packagist.
  6. PHP version requirement — Original draft said “PHP 7.4 or higher (recommend 8.2+).” The current client’s composer.json hard-requires PHP ^8.2 — this isn’t just a recommendation anymore. Source: exposedev/expose composer.json.
  7. Client/server architecture — This is the most substantial correction. The original draft assumed a single beyondcode/expose package provides both the client and a self-hostable server via expose serve/expose publish. As of the current release, the self-hosted server is a separate package (exposedev/server, installed via git clone + composer install, run with expose-server serve <domain>), distinct from the client package (exposedev/expose). The old unified expose serve command on the client now returns an error directing users to the separate expose-server binary. The self-hosting steps in this guide were rewritten around the current two-package structure. Source: Expose Server documentation (starting-the-server); exposedev/server GitHub issue #1 confirming the current client no longer accepts serve.
  8. Free-tier custom subdomains — Original draft implied you could request a custom subdomain on Beyond Code’s free shared server. Current docs state --subdomain requires either Expose Pro/Team or a self-hosted server — the free sharedwithexpose.com tier always assigns a random subdomain per connection. Source: Expose Sharing documentation.
  9. Expose pricing — Original draft didn’t give concrete Expose Pro pricing. Added current figures: Pro $79/user/year, Team $229/team/year (up to 10 users), plus the actual restrictions of the free Hobby tier (time-limited connections, single EU server, random URLs). Source: expose.dev pricing page.
  10. Nginx config — Original draft’s Nginx block listened on port 80 and left SSL to a separate, unspecified Certbot step. Replaced with Expose’s own documented Nginx config, which terminates SSL directly on port 443. Source: Expose Server SSL Support documentation.
  11. Supervisor config — Original draft’s command= path assumed the old unified binary layout. Corrected to point at the separate expose-server binary with the domain argument, matching the current package structure (Expose’s own supervisor example in the docs omits the domain argument, which is likely a documentation gap — it’s included here since the server needs it to know which domain to serve). Source: Expose Server “Keeping the server running with supervisord” documentation.
  12. Laravel integration scope — Original draft only covered Laravel Valet. Added that Valet now supports a third share tool, cloudflared, alongside ngrok and Expose, and that Laravel Herd (Beyond Code’s own environment) ships with Expose built in natively via its settings UI — a more directly relevant integration point than Valet, since Beyond Code builds both tools. Source: Laravel Valet documentation (12.x/13.x); Expose Installation documentation.
  13. Project activity — Added current GitHub stats (roughly 4,500+ stars, 300+ forks, MIT license, latest release 3.2.2, March 2026) to establish the project is actively maintained, which the original draft didn’t substantiate. Source: Packagist (exposedev/expose); GitHub.
  14. Interstitial page nuance — Added, since it wasn’t in the original draft: ngrok’s free-tier browser warning page doesn’t affect programmatic/webhook traffic and can be bypassed on the free tier via a custom User-Agent or header, without upgrading. Source: ngrok Free Plan Limits documentation.
  15. Removed unverifiable/promotional language from the original draft (e.g., unqualified claims that Expose is simply “the ultimate Laravel Valet share alternative”) in favor of neutral, sourced comparisons; stripped file metadata for a clean Markdown deliverable.

Continue from this article into the most relevant product guides and workflows.

Related Topics

#Expose reverse proxy, Laravel Valet share alternative, self-hosted PHP tunnel, Beyond Code Expose vs ngrok, Beyond Code Expose, ngrok alternative for PHP, Laravel local development, PHP developer tools, self-hosted reverse proxy, DigitalOcean Expose tutorial, expose localhost to internet, PHP tunneling solution, Laravel Valet share, Valet share ngrok alternative, replace ngrok in Laravel, open-source ngrok alternative, Expose vs ngrok performance, self-hosted Expose server, DigitalOcean $5 droplet tutorial, local environment sharing, PHP local server, secure tunnel for PHP, reverse proxy in PHP, host your own ngrok, ngrok free tier limitations, expose local server, PHP developer environment, Laravel ecosystem, Laravel deployment, artisan serve alternative, Beyond Code tools, local dev server, webhook testing PHP, local API testing, expose local web server, secure reverse proxy, open source tunnel, PHP tunnel script, custom domain Expose, self host webhook tunnel, bypass ngrok limits, localhost tunneling, developer infrastructure tools, expose local API to internet, share local website, PHP networking, web development proxy, reverse proxy tutorial, Laravel tips and tricks, PHP artisan share, expose client configuration, self-managed PHP proxy, open source developer tools, Laravel dev workflow

Comments