Skip to main content
API gateway

One API gateway. Anywhere your services run.

ngrok collapses routing, WAF, observability, failover, and auth into one gateway. Plus, it works the exact same within your VPC, across multiple clouds, on K8s clusters, or from inside a customer’s network.

  • Calendly
  • Cyera
  • Databricks
  • GitHub
  • Grafana
  • Harvey
  • Hugging Face
  • Mercor
  • Microsoft
  • Okta
  • Open AI
  • Perplexity
  • Ramp
  • Schneider Electric
  • Twilio
  • Vercel
  • Windsurf
  • Zoom
How it works

One agent.
Every environment.

The agent connects outbound on port 443, which means no inbound ports, DNS changes, or per-environment gateway. Traffic flows through ngrok’s cloud and out to wherever your services live.

ngrok is your global entry point to anywhere your APIs run. The same agent runs on a laptop, a VM, a Raspberry Pi, an EC2 box, or a Kubernetes cluster.

Block attacks, rate-limit abusers, and authenticate every request at our cloud. Good traffic goes where it should. Bad traffic never reaches your VPC.

One install, one command, anywhere. Agents create unique URLs for each service that’s accessible only through the front door you control in ngrok’s cloud.

Close every port. Because the agent connects outbound, attackers have no way to poke at your origin servers, which remain fully locked down.

# In front of your API servicengrok http 3000 --url https://api.internal
# In front of your app servicengrok http 8080 --url https://app.internal

Build on an expressive rules system. No more cryptic nginx configs or Lua plugins. Instead, compose rules across teams and offload processing to ngrok’s cloud.

Route to new services, simply. Append a new forward-internal rule to your existing policy and you’re off, no matter where the service runs.

on_http_request:  # Block anonymous proxies, Tor exit nodes, and threat lists  - expressions:      - >-        'proxy.anonymous' in conn.client_ip.categories        || 'blocklist.org.firehol.level_1' in conn.client_ip.categories    actions:      - type: deny   # Rate-limit abusers per client IP  - actions:      - type: rate-limit        config:          name: per-ip          algorithm: sliding_window          capacity: 100          rate: 60s          bucket_key:            - conn.client_ip   # Block OWASP top 10 attacks inbound to your apps  - actions:      - type: owasp-crs-request        config:          on_error: deny   # Add security and tracing headers on the way in  - actions:      - type: add-headers        config:          headers:            strict-transport-security: max-age=31536000   # Route to the internal endpoints you created with the agent  - expressions:      - req.url.path.startsWith('/v1/')    actions:      - type: forward-internal        config:          url: https://api.internal  - actions:      - type: forward-internal        config:          url: https://app.internal on_http_response:  # Block OWASP top 10 attacks outbound from your apps  - actions:      - type: owasp-crs-response        config:          on_error: deny   # Compress responses on the way out  - actions:      - type: compress-response

Why ngrok?

Stop running five tools to do one job. Routing, WAF, rate limiting, auth, and certs all live in one gateway.

Add modern controls to APIs you can’t rewrite. Enforce JWT validation, OAuth, rate limits, and IP rules at our network.

Works the same everywhere your APIs do. AWS, Azure, on-prem, K8s, and customer networks behind one gateway.

Platform owns the front door, teams own their routes. Layered policy that scales from day one.

Self-service from $20/month.

Pay only for what you use and scale as you grow. It really can be that simple.

See pricing
Traffic Policy

Bring your messy config.
Leave with something far simpler.

This is a real production nginx config from a real developer. They shed a ton of code and complexity on their way to something that's far more expressive and elegant. And yes, you can have this too.

user www-data;worker_processes auto;pid /run/nginx.pid;include /etc/nginx/modules-enabled/*.conf;worker_rlimit_nofile 65536; events {    worker_connections 4096;    multi_accept on;    use epoll;    accept_mutex off;} http {    # Anti-indexing and security headers    add_header X-Robots-Tag "noindex, nofollow, noarchive, nosnippet" always;    add_header X-Frame-Options "DENY" always;    add_header X-Environment "production" always;     # Basic settings - production optimized    sendfile on;    tcp_nopush on;    tcp_nodelay on;     # Hash table sizes    types_hash_max_size 4096;    types_hash_bucket_size 128;    server_names_hash_bucket_size 128;    server_names_hash_max_size 2048;     server_tokens off;     # Memory optimizations    open_file_cache max=10000 inactive=20s;    open_file_cache_valid 30s;    open_file_cache_min_uses 2;    open_file_cache_errors on;     # Client buffers and timeouts    client_body_buffer_size 128k;    client_header_buffer_size 4k;    client_max_body_size 100m;    large_client_header_buffers 8 32k;    client_body_timeout 60;    client_header_timeout 60;    keepalive_timeout 120;    send_timeout 60;    include /etc/nginx/mime.types;    default_type application/octet-stream;     # Enhanced logging format    log_format main '$remote_addr - $remote_user [$time_local] "$request" '                    '$status $body_bytes_sent "$http_referer" '                    '"$http_user_agent" "$http_x_forwarded_for" '                    'rt=$request_time uct="$upstream_connect_time" '                    'uht="$upstream_header_time" urt="$upstream_response_time"';    access_log /var/log/nginx/access.log main buffer=512k flush=1m;    error_log /var/log/nginx/error.log warn;     # Gzip compression    gzip on;    gzip_comp_level 6;    gzip_min_length 1024;    gzip_proxied any;    gzip_vary on;    gzip_disable "msie6";    gzip_types application/json application/xml text/css               text/javascript image/svg+xml font/ttf;     include /etc/nginx/conf.d/*.conf;    include /etc/nginx/sites-enabled/*;} # /etc/nginx/sites-enabled/api.example.comupstream backend_api {    server 127.0.0.1:3000;    keepalive 32;} server {    server_name api.example.com;    listen 443 ssl http2;     # TLS managed by Certbot    ssl_certificate     /etc/letsencrypt/live/api.example.com/fullchain.pem;    ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;    include /etc/letsencrypt/options-ssl-nginx.conf;    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;    ssl_stapling on;    ssl_stapling_verify on;     # Security headers    add_header Strict-Transport-Security "max-age=31536000; preload" always;    add_header X-Content-Type-Options nosniff always;    add_header X-XSS-Protection "1; mode=block" always;    add_header Referrer-Policy "strict-origin-when-cross-origin" always;     # Main proxy    location / {        proxy_pass http://backend_api;        proxy_set_header Host $host;        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 $scheme;        proxy_http_version 1.1;        proxy_set_header Upgrade $http_upgrade;        proxy_set_header Connection "upgrade";         # Proxy timeouts and buffering        proxy_connect_timeout 120s;        proxy_send_timeout 180s;        proxy_read_timeout 300s;        proxy_buffering on;        proxy_buffer_size 4k;        proxy_buffers 8 4k;        proxy_busy_buffers_size 8k;         # Upstream resilience        proxy_next_upstream error timeout invalid_header http_502 http_503 http_504;        proxy_next_upstream_timeout 120s;        proxy_next_upstream_tries 3;    }     # Static asset caching    location ~* \.(jpg|jpeg|png|gif|ico|css|js|pdf|txt)$ {        expires 1y;        add_header Cache-Control "public, immutable";        proxy_pass http://backend_api;    }     # Health check    location /health {        access_log off;        proxy_pass http://backend_api;        proxy_connect_timeout 10s;        proxy_send_timeout 10s;        proxy_read_timeout 10s;    }     # Block hidden files    location ~ /\. {        deny all;        access_log off;        log_not_found off;    }} # HTTP -> HTTPS redirect (managed by Certbot)server {    if ($host = api.example.com) {        return 301 https://$host$request_uri;    }    listen 80;    server_name api.example.com;    return 404;}
Every environment

Wherever your APIs live,
ngrok meets them there.

Cloud-provider gateways stop at their own cloud. ngrok installs and runs the exact same way in every environment to put one hostname in front of your services wherever they live.

Go multicloud without per-cloud gateways

The agent runs in each cloud and connects outbound. One hostname spans AWS, Azure, and GCP. Endpoint Pools route traffic by region and fail over when a cloud goes down. No DNS headaches or managing a daisy chain of load balancers.

Ingress to multiple clusters

Each cluster runs the ngrok Operator and connects outbound. Route north-south traffic across all of them under one hostname and scale up as easily as adding more replicas of a pod.

Add connectivity to private and on-prem services

The ngrok agent connects outbound from any network and on any hardware, so services once reachable only through a VPN-esque kludge open up to specific identities.

Reach into customer networks, too

Embed the agent in software you ship. Each customer install becomes a routable origin under one gateway, whether you have one install or ten thousand.

Composable policy

Platform owns the front door.
Service teams own their routes.

Most gateways force you to choose between centralized control and team velocity. ngrok lets the right team manage the right policy and composes it all together on our cloud.

Fig. 1 – Platform team’s front door policy
on_http_request:  # Block anonymous proxies and Tor exit nodes  - expressions:      - "'proxy.anonymous' in conn.client_ip.categories"    actions:      - type: deny   # Rate-limit abusers per client IP  - actions:      - type: rate-limit        config:          name: per-ip          algorithm: sliding_window          capacity: 100          rate: 60s          bucket_key:            - conn.client_ip   # Block OWASP top 10 attacks at ngrok's cloud  - actions:      - type: owasp-crs-request        config:          on_error: deny   # ... and a whole bunch more
Fig. 2 – The API team’s api.yaml policy
on_http_request:  # Rewrite legacy /v1/* paths to /v2/*  - expressions:      - req.url.path.startsWith('/v1/')    actions:      - type: url-rewrite        config:          from: ^/v1/(.*)$          to: /v2/$1   # Ship a structured event for every request  # to the observability pipeline  - actions:      - type: log        config:          metadata:            team: api-platform            method: ${req.method}            path: ${req.url.path}            client_ip: ${conn.client_ip}            country: ${conn.geo.country_code}            user_agent: ${req.user_agent.name}            is_bot: ${req.user_agent.is_bot}

Stop running five tools. Start running one gateway.

Free to start. Three minutes from signup to first request.

Watch

Why teams are switching gateways

A quick tour of the routing, auth, and WAF you’d otherwise wire together yourself.

Frequently asked questions