Skip to content

Deploy an edge tunnel gateway with the edge-tunnel-gateway template

Deployment

Deploy an edge tunnel gateway with the edge-tunnel-gateway template

Stand up a Pangolin tunnel endpoint on one Quake AI instance using the validated OpenTofu template edge-tunnel-gateway. You apply the template, complete the Pangolin dashboard setup, connect a Newt tunnel client from a private machine, register a resource with TLS on a domain, add an access policy, and confirm the service is reachable only through the endpoint.

The tunnel endpoint holds the only public floating IP. The private service stays behind a subnet with no inbound ports open and no address on the internet. You operate the endpoint and the tunnel client yourself; this is a self-hosted tunnel gateway you run in one region, not a global edge network or DDoS scrubber.

BrowserFloating IP80 / 44351820 UDPEndpoint VMPangolinPrivate VMno floating IPTraefikGerbilWireGuardNewt clientApp :8080 localhostHTTPSWireGuard tunnelproxied resource
Click to zoom
What you'll build: Pangolin on an endpoint VM with a floating IP routes authenticated HTTPS over a WireGuard tunnel to a private service reached by Newt

Monthly cost estimate

Pricing calculator ↗

Sized as a custom package on shared vCPU.

Starting template$31.20/mo

Partial estimate: Monthly total for the required template above. Use the configurator below to add optional pieces and see the total update.

What each resource is for

Tunnel

s1a.small · 2 shared vCPU, 2 GiB RAM, 0.5 Gbps

$16.50/mo

s1a.small

s1a.small · 2 shared vCPU, 2 GiB RAM, 0.5 Gbps

$16.50/mo

Compute shown per role at custom-package rates ($29/dedicated vCPU, $7.25/shared vCPU, $1/GiB RAM). The headline above is the billed total: the cheaper of a named plan and the custom package, plus add-ons.

Included in baseline

s1a.small

2 shared vCPU, 2 GiB RAM, 0.5 Gbps

$16.50

s1a.small

2 shared vCPU, 2 GiB RAM, 0.5 Gbps

$16.50

Compute + RAM rate basis

4 vCPU + 4 GiB RAM at $29/dedicated vCPU, $7.25/shared vCPU, $1/GiB RAM (regular). Totals apply the flat −$5/mo package promotion.

—

Block storage (40 GiB)

40 GiB at $0.08/GiB/mo

$3.20

Public IP (included)

1 included with the custom package

$0.00

Package promotional discount

Flat −$5.00/mo on the custom package (same promotion as named plans).

$-5.00

Included at no charge

These line items are zero on Quake AI. Many other providers meter them separately.

Data transfer (inbound and outbound)

Unlimited data transfer on every plan; Quake AI does not meter per-GB egress.

AWS, GCP, and Azure meter outbound transfer per GB. DigitalOcean and Hetzner include an allowance on compute plans, then charge overage.

Learn more
$0.00

Private networking

Private networks, subnets, Neutron routers, and security groups are included with the plan.

VPC objects are usually free to create elsewhere, but NAT gateways bill hourly plus per-GB processed. Quake AI uses router SNAT with no separate NAT line item.

$0.00

Control-plane API requests

OpenStack API calls for provisioning and management are included.

Some managed services on other clouds meter API calls or charge for premium control-plane features.

$0.00

Pricing data last validated: . For current rates, check quake.ai/pricing.

Prerequisites#

You need:

  • OpenTofu 1.6.0 or later (or Terraform 1.6.0 or later) installed locally.
  • Your OpenStack credentials sourced into the shell (source openrc.sh). See the OpenStack CLI guide.
  • An SSH keypair that already exists in your project. Record its name for the key_name variable.
  • A copy of the edge-tunnel-gateway template directory from the template reference page.
  • Two hostnames on a domain you control: one for the Pangolin dashboard (for example pangolin.example.com) and one for the resource you expose (for example app.example.com). Both DNS A records point at the endpoint floating IP. Follow How to point a domain at a Quake AI resource.

Step 1: Apply the tunnel endpoint template#

Copy the template's example variables file and set key_name, domain, base_domain, and letsencrypt_email:

bash
cp terraform.tfvars.example terraform.tfvars
HCL
key_name            = "YOUR_KEY_NAME"
domain              = "pangolin.example.com"
base_domain         = "example.com"
letsencrypt_email   = "[email protected]"

Point pangolin.example.com at the floating IP before Traefik requests a certificate. If DNS is not ready yet, apply with domain empty first, test over HTTP on the floating IP, then update domain and re-apply.

Initialize, preview, and apply:

bash
tofu init
tofu plan
tofu apply

OpenTofu provisions a private network, router, security group, data volume, endpoint instance, and floating IP. cloud-init installs Docker, starts Pangolin, Gerbil, and Traefik, generates a server secret on first boot, and writes setup instructions to /root/tunnel-admin-credentials.

Record the outputs:

bash
tofu output floating_ip
tofu output dashboard_url
tofu output tunnel_endpoint
tofu output admin_hint

Wait three to five minutes for cloud-init and container health checks to finish.

Step 2: Complete Pangolin initial setup#

No admin credential ships in the repository. SSH to the endpoint and read the generated credentials file:

bash
ssh ubuntu@YOUR_FLOATING_IP 'sudo cat /root/tunnel-admin-credentials'

Note the dashboard_url, setup_token, and tunnel_endpoint values.

In a browser, open the dashboard URL from the credentials file (for example https://pangolin.example.com/auth/initial-setup). Paste the setup token, create the admin account, and sign in.

Port 3001 is not open in the endpoint security group. If you need the raw dashboard API during troubleshooting, open an SSH tunnel:

bash
ssh -L 3001:127.0.0.1:3001 ubuntu@YOUR_FLOATING_IP

Step 3: Stand up a private service and connect Newt#

This walkthrough runs the tunnel client on a private Quake AI instance with no floating IP. The same Newt container runs on a home lab or any machine behind CGNAT that has outbound internet access.

  1. Create a security group for the private host. Allow SSH from your workstation while you configure it; do not attach a floating IP.
bash
openstack security group create tunnel-private-sg \
  --description "Private service host; no inbound application ports from the internet"
openstack security group rule create \
  --protocol tcp --dst-port 22 --remote-ip YOUR_IP/32 tunnel-private-sg
  1. Launch a small instance on any private network that reaches the internet outbound (the template's edge-tunnel-gateway-net works). Serve a test page on port 8080:
bash
cat > private-cloud-init.yaml <<'EOF'
#cloud-config
package_update: true
packages:
  - nginx
runcmd:
  - |
    echo 'private app ok' > /var/www/html/index.html
    printf '%s\n' 'server {' '  listen 8080;' '  root /var/www/html;' '}' > /etc/nginx/sites-available/default
    systemctl restart nginx
EOF

openstack server create \
  --flavor s1a.small \
  --image Ubuntu-24.04 \
  --key-name YOUR_KEY_NAME \
  --network edge-tunnel-gateway-net \
  --security-group tunnel-private-sg \
  --user-data private-cloud-init.yaml \
  tunnel-private

Wait until the instance reports ACTIVE. SSH to the private host and confirm the app responds locally:

bash
curl -s http://127.0.0.1:8080/

The response body should include private app ok.

  1. In the Pangolin dashboard, create an organization if prompted, then create a Site for your private network. Pangolin prints a site ID and secret for the Newt client.

  2. On the private host, run Newt with the site credentials and your dashboard URL:

bash
docker run -d --name newt --restart unless-stopped --network host \
  -e PANGOLIN_ENDPOINT=https://pangolin.example.com \
  -e NEWT_ID=YOUR_SITE_ID \
  -e NEWT_SECRET=YOUR_SITE_SECRET \
  fosrl/newt

Return to the dashboard and confirm the site reports Online. Newt maintains an outbound WireGuard tunnel to tunnel_endpoint (UDP port 51820 on the floating IP).

Step 4: Register a resource with TLS#

In the Pangolin dashboard, add a Resource (or Application) for the private service:

  1. Set the public hostname to app.example.com (a name under your base_domain).
  2. Set the internal target to http://127.0.0.1:8080 on the site where Newt runs.
  3. Enable HTTPS for the resource. Traefik on the endpoint obtains a certificate through the HTTP-01 challenge once DNS resolves.

Create a DNS A record for app.example.com pointing at YOUR_FLOATING_IP. Wait until it resolves:

bash
dig +short app.example.com

From your workstation, request the resource hostname. Pangolin should route traffic over the tunnel:

bash
curl -s -o /dev/null -w "%{http_code}\n" https://app.example.com/

Until you add an access policy in the next step, the status code may be 200 or 302 depending on your resource defaults. A 502 usually means Newt is offline or the internal target is wrong; check the site status in the dashboard and docker logs newt on the private host.

Step 5: Add an access policy#

In the Pangolin dashboard, open the resource you created and attach an access policy. For a first deployment, One-time PIN or Email PIN is enough to confirm identity-aware access without wiring an OIDC provider.

Save the policy. Unauthenticated browser requests should redirect to the Pangolin login or PIN flow instead of reaching the app directly.

Verify from your workstation without a session:

bash
curl -s -o /dev/null -w "%{http_code}\n" https://app.example.com/

The status code should be 302 or 401, not 200 with the app body. Sign in through a browser, complete the PIN or SSO step Pangolin presents, then load https://app.example.com/ again. The page should show private app ok.

Step 6: Confirm the private service is not reachable directly#

The private host has no floating IP and no inbound application ports open to the internet. Confirm you cannot reach the app on the private address from your workstation:

bash
curl -s -o /dev/null -w "%{http_code}\n" --connect-timeout 3 http://PRIVATE_INSTANCE_IP:8080/ || echo "unreachable"

The command should time out or fail to connect. Traffic to the app flows only through the authenticated tunnel endpoint at https://app.example.com/.

If you run Newt on a home network instead of a Quake AI private instance, the same check applies: the home router keeps port 8080 closed inbound; only the outbound tunnel carries traffic.

What you built#

  • Applied the edge-tunnel-gateway template to provision a private network, endpoint security group, data volume, Pangolin stack, and floating IP
  • Completed Pangolin initial setup and signed in to the dashboard
  • Connected Newt from a private host over an outbound WireGuard tunnel
  • Registered a resource at app.example.com with TLS terminated at the endpoint
  • Added an access policy so unauthenticated requests do not reach the private app
  • Confirmed the private service is not reachable directly from the internet

Scope of this deployment#

This endpoint runs in one region on a VM you operate. It exposes a service through your Quake AI VM; it is not a global edge network and it does not absorb volumetric DDoS traffic. Quake AI has no anycast, no global PoPs, and no first-party CDN. For geographic distribution and edge absorption, front the origin with a third-party CDN.

The walkthrough adds a second small instance for the private service host; that VM is not part of the OpenTofu template. Newt also runs on a home lab or any outbound-only network when you prefer not to host the private side on Quake AI.

Next steps#

Clean up#

When you no longer need the deployment, destroy the OpenTofu stack and delete the private host you created in step 3:

bash
tofu destroy
openstack server delete tunnel-private
openstack security group delete tunnel-private-sg

On the private host before deletion, stop Newt with docker rm -f newt. Remove DNS A records for pangolin.example.com and app.example.com.

Was this page helpful?