wombat In this guide we’ll expose a web service running on a machine behind a NAT to the public internet using Wombat.

By the end of this tutorial you’ll have:

  • A wombat-server running on a VPS
  • A wombat-agent running on your local machine
  • A TLS-encrypted tunnel connecting the two
  • A local web service accessible from anywhere on the internet

If you’re interested in how Wombat works internally, check out my previous post, Building Wombat: A Reverse TCP Tunnel. This guide focuses purely on getting started.

About Wombat

Wombat is a lightweight reverse TCP tunnel that securely exposes local TCP services behind a NAT to the public internet.

It consists of two components.

wombat-server

The wombat-server runs on a publicly accessible machine, such as a VPS. It accepts incoming client connections and forwards them through a secure tunnel.

wombat-agent

The wombat-agent runs on the machine hosting your local services. It establishes a persistent TLS-encrypted tunnel to the server and forwards traffic between your local service and connected clients.

Installing Wombat

Download the latest release from GitHub, or use the guided installer on Unix-like systems.

Depending upon your requirement, the wombat-server & wombat-agent can be easily installed by getting the right binary from the project release section. For unix like systems, we can easily install it using the guided installer by running

curl -fsSL https://raw.githubusercontent.com/ajsqr/wombat/main/install.sh | bash

I’m going to run this on my mac to install wombat-agent and also on my ec2 instance for installing the wombat-server.

Which component would you like to install?
  1) Agent
  2) Server
Choice [1-2]: 1
Downloading wombat-agent...
Extracting...
Installing to /usr/local/bin (you may be prompted for your password)...
Password:

✅ Successfully installed wombat-agent

Binary:
  /usr/local/bin/wombat-agent

Config directory:
  /Users/username/Library/Application Support/wombat

Run:
  wombat-agent version

I can then verify that installer worked by running

wombat-agent version
wombat-agent 0.2.0 build f273555

Depending on the latest version during the time, you may get a different version.

Setting up wombat-server

The wombat-server and wombat-agent communicate over a persistent TCP tunnel. Since this tunnel carries all traffic between your local service and the internet, Wombat requires it to be secured using TLS.

In this section we’ll create a small private Certificate Authority (CA), use it to issue a certificate for the server, and configure wombat-server to use it.

Note

For production deployments you may already have your own PKI or certificates signed by a trusted CA. For this tutorial we’ll generate everything ourselves so that you can get started quickly.

Create a working directory

On the machine running wombat-server, create a directory to store the certificates.

mkdir certs
cd certs

Generate a Certificate Authority

First, generate a private key for the Certificate Authority.

openssl genrsa -out ca.key 4096

Next, create a self-signed CA certificate.

openssl req \
    -x509 \
    -new \
    -nodes \
    -key ca.key \
    -sha256 \
    -days 3650 \
    -out ca.crt \
    -subj "/CN=Wombat CA"

This CA will be used to sign the server certificate. Later, we’ll copy ca.crt to the machine running wombat-agent so it can verify the server during the TLS handshake.


Generate the server certificate

Generate the server’s private key.

openssl genrsa -out server.key 4096

Create a file named server.cnf.

[req]
default_bits = 4096
prompt = no
default_md = sha256
distinguished_name = dn
req_extensions = req_ext

[dn]
CN = localhost

[req_ext]
subjectAltName = @alt_names

[alt_names]
DNS.1 = localhost

The Common Name (CN) and Subject Alternative Name (SAN) specify the hostname that the certificate is valid for. In this tutorial we’ll use localhost, so the agent must later be configured with the same server name.

Generate a Certificate Signing Request.

openssl req \
    -new \
    -key server.key \
    -out server.csr \
    -config server.cnf

Finally, sign the server certificate using the CA we created earlier.

openssl x509 \
    -req \
    -in server.csr \
    -CA ca.crt \
    -CAkey ca.key \
    -CAcreateserial \
    -out server.crt \
    -days 365 \
    -sha256 \
    -extensions req_ext \
    -extfile server.cnf

Your directory should now contain the following files.

certs/
├── ca.crt
├── ca.key
├── ca.srl
├── server.cnf
├── server.crt
├── server.csr
└── server.key

Only two of these files are required by wombat-server:

  • server.crt — The TLS certificate presented to connecting agents.
  • server.key — The private key corresponding to the certificate.

The ca.crt file will be copied to the machine running wombat-agent in the next section.


Configure wombat-server

Wombat automatically looks for its configuration file in the platform’s user configuration directory.

PlatformConfiguration Directory
Linux~/.config/wombat/
macOS~/Library/Application Support/wombat/
Windows%AppData%\wombat\

Create a file named server-config.json.

{
  "certPath": "/home/ec2-user/certs/server.crt",
  "keyPath": "/home/ec2-user/certs/server.key",
  "tunnels": [
    {
      "name": "echo",
      "tunnel": "0.0.0.0:4001",
      "public": "0.0.0.0:8001",
      "tokenName": "echo"
    }
  ]
}

Let’s briefly understand what each field means.

FieldDescription
certPathPath to the server TLS certificate.
keyPathPath to the server private key.
nameA unique name identifying the tunnel.
tunnelAddress where the agent connects to establish the tunnel.
publicAddress where internet clients connect.
tokenNameName of the environment variable containing the shared secret.

In this configuration:

  • Agents establish a TLS tunnel on port 4001.
  • Internet clients connect to port 8001.
  • Requests received on port 8001 will eventually be forwarded through the tunnel to a local service running on the machine hosting wombat-agent.

We’re almost ready to start the server. Before we do that, we’ll configure the agent and copy the CA certificate so it can verify the server’s identity.

Setting up wombat-agent

With the server configured, let’s set up the agent.

The wombat-agent runs alongside the service you want to expose. It establishes a persistent TLS connection to wombat-server and forwards traffic between your local service and connected internet clients.

Unlike the server, the agent doesn’t require its own certificate. Instead, it verifies the server’s identity using the Certificate Authority (CA) certificate we generated earlier.


Copy the CA certificate

Copy the ca.crt file from the server to the machine running wombat-agent.

How you transfer the file is entirely up to you. For example, using scp:

scp ec2-user@<your-server-ip>:~/certs/ca.crt ~/certs/

After copying the file, your local certificate directory should look something like this.

certs/
└── ca.crt

This certificate will be used to verify the identity of wombat-server whenever the agent establishes a TLS connection.


Configure wombat-agent

Like the server, the agent automatically looks for its configuration file inside the Wombat configuration directory.

PlatformConfiguration Directory
Linux~/.config/wombat/
macOS~/Library/Application Support/wombat/
Windows%AppData%\wombat\

Create a file named agent-config.json.

{
  "caCertPath": "/Users/username/certs/ca.crt",
  "serverName": "localhost",
  "tunnels": [
    {
      "name": "echo",
      "tunnel": "<your-server-ip>:4001",
      "local": "127.0.0.1:8080",
      "tokenName": "echo"
    }
  ]
}

Let’s walk through each field.

FieldDescription
caCertPathPath to the CA certificate used to verify the server.
serverNameExpected hostname in the server’s TLS certificate.
nameName of the tunnel. This must match the server configuration.
tunnelAddress of the tunnel listener on wombat-server.
localLocal service that Wombat should expose.
tokenNameName of the environment variable containing the shared secret.

There are a few important things to note.

serverName

Earlier, when generating the server certificate, we configured the certificate with:

CN = localhost

[alt_names]
DNS.1 = localhost

Because of this, the agent must use:

"serverName": "localhost"

During the TLS handshake, the agent verifies that the certificate presented by the server is valid for this hostname. If they don’t match, the connection will be rejected.

name

The tunnel name uniquely identifies a tunnel.

Both the server and the agent must use the same tunnel name.

"name": "echo"

tunnel

This is the address of the tunnel listener running on wombat-server.

"tunnel": "<your-server-ip>:4001"

Replace <your-server-ip> with the public IP address (or domain name) of your VPS.

local

The local field specifies the service you want to expose.

"local": "127.0.0.1:8080"

In this tutorial we’ll expose a simple HTTP server running on port 8080, but this can point to any TCP service, including:

  • Web applications
  • REST APIs
  • SSH
  • PostgreSQL
  • Redis
  • MQTT
  • Game servers

Wombat is completely protocol agnostic—it simply forwards TCP streams.


Configure the shared secret

In addition to TLS, every tunnel is protected using a shared secret.

Notice that both configuration files contain:

"tokenName": "echo"

This is not the secret itself.

Instead, it tells Wombat which environment variable to read when authenticating the tunnel.

On both the server and the agent, define the same environment variable.

Linux / macOS

export echo="correct-horse-battery-staple"

Windows PowerShell

$env:echo="correct-horse-battery-staple"

When the agent connects, both sides independently read the value of the echo environment variable and verify that they match before allowing the tunnel to become active.

With both configuration files complete, we’re finally ready to start the server, connect the agent, and expose our first local service to the internet.

Running Wombat

With both components configured, we’re finally ready to establish the tunnel.

Configure your firewall

Our server configuration exposes two ports.

PortPurpose
4001Tunnel connection from wombat-agent
8001Public endpoint exposed to internet clients

The public port (8001) must be reachable from the internet.

The tunnel port (4001) should only accept connections from machines running wombat-agent. If you’re deploying to AWS, Azure, GCP, or another cloud provider, it’s recommended to restrict this port in your security group or firewall to trusted IP addresses instead of exposing it publicly.

For this tutorial, make sure both ports are reachable so the agent can establish the tunnel.


Start wombat-server

On your VPS, simply run:

wombat-server run

Once the agent connects, you should see output similar to the following.

{"time":"2026-07-27T06:40:19.323017348Z","level":"INFO","msg":"creating tunnel connection","channel":"echo"}
{"time":"2026-07-27T06:40:21.647700101Z","level":"INFO","msg":"authenticating tunnel connection","channel":"echo"}
{"time":"2026-07-27T06:40:21.696547556Z","level":"INFO","msg":"successfully authenticated tunnel","channel":"echo"}
{"time":"2026-07-27T06:40:21.696575415Z","level":"INFO","msg":"successfully established a tunnel","channel":"echo"}

At this point the server is ready to proxy traffic through the secure tunnel.


Start a local service

Before starting the agent, let’s create a simple web server to expose.

Python ships with a lightweight HTTP server that’s perfect for testing.

python3 -m http.server 8080

You can verify it’s running by visiting:

http://localhost:8080

Start wombat-agent

On the machine hosting your local service, run:

wombat-agent run

If everything is configured correctly, you’ll see:

{"time":"2026-07-27T12:10:21.530942+05:30","level":"INFO","msg":"attempting to connect to the tunnel","channel":"echo"}
{"time":"2026-07-27T12:10:21.630683+05:30","level":"INFO","msg":"authenticating tunnel","channel":"echo"}
{"time":"2026-07-27T12:10:21.630767+05:30","level":"INFO","msg":"successfully authenticated tunnel connection","channel":"echo"}
{"time":"2026-07-27T12:10:21.630778+05:30","level":"INFO","msg":"successfully established tunnel connection","channel":"echo"}

Congratulations! 🎉

Your VPS and local machine are now connected by a secure TLS tunnel.


Common authentication error

If you instead see an error like:

{"time":"2026-07-27T12:09:42.4269+05:30","level":"ERROR","msg":"tunnel connection failed","channel":"echo","error":"error during handshake : token not set"}

it means the shared secret could not be found.

Double-check that you’ve exported the environment variable specified by tokenName in both your server-config.json and agent-config.json.

For example, if your configuration contains:

"tokenName": "echo"

then both the server and the agent must have an environment variable named echo.

Linux/macOS:

export echo="correct-horse-battery-staple"

Windows PowerShell:

$env:echo="correct-horse-battery-staple"

After setting the environment variable, restart the component and try again.


Test the tunnel

With both components running, open a browser and visit:

http://<your-server-ip>:8001

Or use curl:

curl http://<your-server-ip>:8001

Although the request is sent to your VPS, Wombat transparently forwards it through the encrypted tunnel to the web server running on your local machine.

If everything has been configured correctly, you should see the directory listing served by Python’s HTTP server.

Congratulations! You’ve successfully exposed a local service behind a NAT to the public internet using Wombat.