How to set up SSH tunneling so OpenClaw never touches the public internet

Your OpenClaw gateway is reachable from the public internet right now. That means anyone who finds the port can try to reach it. This article covers the two-step fix: binding the gateway to the loopback address (127.0.0.1, the address that means “this machine only, no outside connections”). This network isolation approach aligns with NIST SP 800-190 guidance on restricting service exposure to localhost where external access is not required so it stops answering public requests, then setting up an SSH tunnel so you can still access it from your local machine. No firewall rules required. No VPN required. Takes under 20 minutes on any setup. Start at Step 1.

What you need: An OpenClaw agent running on a remote server (VPS, short for Virtual Private Server, meaning a rented Linux machine in a data center, not your own laptop or desktop). SSH access to that server already working (SSH is the tool you use to connect to your server via terminal, already confirmed working if you have ever logged in with a terminal command like ssh user@your-server-ip). A terminal on your local machine (Mac Terminal, Windows Terminal, or Linux terminal). No other tools required.
TL;DR: Change gateway.bind in your OpenClaw config from 0.0.0.0 (see what 0.0.0.0 actually exposes) to 127.0.0.1. Then connect to your server with ssh -L 18789:localhost:18789 -N user@your-server-ip. Access OpenClaw at localhost:18789 on your local machine. The gateway is now unreachable from the public internet and only accessible through your encrypted SSH connection.

Time this takes: Under 20 minutes for most setups. The bind change is one config field. The SSH tunnel command is one line. Making it persistent (so it survives disconnects) adds 10 minutes.

Why your OpenClaw gateway is exposed right now

Every network-connected server has a concept called a bind address: the address a program listens on for incoming connections. When a program binds to 0.0.0.0, it means “accept connections from any network interface, including the public internet.” When it binds to 127.0.0.1, it means “accept connections only from this machine itself.” No program bound to 127.0.0.1 can be reached from the outside, regardless of firewall settings.

OpenClaw’s gateway defaults to binding on 0.0.0.0. That means your OpenClaw instance has been reachable from any IP address since you installed it, on whatever port (a numbered door on the server that programs use to send and receive network traffic) your gateway uses (default: 18789). Anyone who scans that port can find it.

The fix is not just adding a firewall rule. A firewall blocks traffic after it arrives at your server’s network stack. Binding to 127.0.0.1 means the gateway never opens that door in the first place. It is a stronger guarantee.

Step 1: Check your current gateway.bind setting

Before changing anything, verify what your current bind address is. If you skip this check and change a setting that is already correct, you will restart the gateway unnecessarily. Ask your agent:

What is the current value of gateway.bind in your config? Show me the full gateway config block.

If the response shows gateway.bind: "127.0.0.1", your gateway is already bound to loopback. Skip to Step 2 to set up the SSH tunnel.

If the response shows gateway.bind: "0.0.0.0" or no bind field at all (the default is 0.0.0.0), proceed with the change below.

Manual path: SSH into your server and run: cat ~/.openclaw/openclaw.json | grep -A5 gateway. Look for the bind field in the gateway section. If it is absent, the default is 0.0.0.0.

Step 2: Change gateway.bind to 127.0.0.1

This is the change that stops OpenClaw from accepting public connections. After this change, the gateway only accepts connections from the server itself. That means you will lose browser access to OpenClaw at your VPS’s public IP once this is applied. The SSH tunnel in Step 3 is how you get access back.

Brick risk: Changing gateway.bind to 127.0.0.1 and restarting the gateway will make OpenClaw unreachable from its current URL. If the SSH tunnel in Step 3 is not set up before you restart, you will need console access to your server to recover. Set up and test the tunnel FIRST. Then apply the bind change. Then restart.

Write: Note the change you are making and your server’s SSH login details before proceeding.
Test: Complete Step 3 (tunnel setup) and confirm it works before restarting the gateway.
Implement: Apply the config change and restart only after the tunnel is confirmed working.

Ask your agent to make the change:

Set gateway.bind to “127.0.0.1” in my config. Show me the exact change before applying it. Do not restart the gateway yet.

Your agent will show the config change for confirmation. Approve it, but do not restart the gateway until Step 3 is complete and tested.

Manual path: SSH into your server, open ~/.openclaw/openclaw.json in a text editor, find the gateway section, and set or add "bind": "127.0.0.1". Save the file. Do not restart the gateway yet.

Step 3: Set up the SSH tunnel

An SSH tunnel (also called a local port forward) is a private corridor from your local machine through the encrypted SSH connection to a port on the remote server. Once the tunnel is active, connecting to localhost:18789 on your laptop sends the request through SSH to localhost:18789 on your server. OpenClaw receives it as a local connection and responds. Nothing travels over the public internet unencrypted. Nothing is accessible to anyone without your SSH credentials.

SSH (Secure Shell) is a protocol for encrypted remote access to servers. You already have it if you’ve connected to your server via terminal. SSH keys are a way of authenticating to SSH without a password: a private key file on your machine proves your identity to the server. If you currently log in with a password, the tunnel still works, but switching to key-based auth is stronger. Ask your agent about that separately if needed.

Ask your agent for the exact tunnel command for your setup:

What SSH command should I run on my local machine to create a tunnel to your gateway? I need the exact command including my server’s IP address and my SSH username. Assume my gateway is on port 18789.

The command will look like this:

Manual path (the actual command to run on your local machine):

ssh -L 18789:localhost:18789 -N your-username@your-server-ip

Replace your-username with your server’s SSH username (often ubuntu, node, or root) and your-server-ip with your server’s IP address. The -N flag means “don’t run a remote command, just hold the tunnel open.” The terminal will appear to hang. That means the tunnel is active.

Run this command in a terminal on your local machine. Leave it running. Then open a browser on your local machine and go to http://localhost:18789. If the dashboard loads, the tunnel is working. If you see “connection refused” or a blank page, the tunnel is not active. Check that the terminal with the ssh command is still open and the command is still running. If it exited with an error, re-run it and check for typos in the username or IP address.

On Windows: Run the command in Windows Terminal (PowerShell or Command Prompt) or Git Bash. The command is identical. If you are using WSL2, run it from Windows Terminal rather than inside WSL2 to avoid port mapping issues between the WSL2 network and your Windows browser.

Step 4: Confirm the tunnel works, then restart the gateway

Before applying the bind change, confirm the tunnel is working in its current state. If the dashboard loads at localhost:18789, the tunnel is set up correctly.

What is not affected by this change: Discord, Telegram, Signal, and other messaging integrations connect outbound from your server to those platforms. They do not use the gateway port and continue working normally after the bind change. The OpenClaw mobile companion app (iOS/Android) connects via a separate pairing protocol, not the gateway port. If you use a mobile app, it is also unaffected. Only browser-based access to the gateway URL changes.

Now restart the gateway to apply the bind change:

Restart the OpenClaw gateway to apply the bind config change.

After the restart, refresh localhost:18789 in your browser. If the dashboard loads, the tunnel is working and the gateway is now bound to loopback. If the page does not load:

  • Check that your SSH tunnel is still running (the terminal should still appear to be hanging)
  • If the tunnel closed, re-run the ssh command and try again
  • If neither works, SSH directly into your server and check the gateway status: openclaw gateway status
To check whether your tunnel is currently running on Mac/Linux:

ss -tlnp | grep 18789   # Linux
lsof -i :18789           # Mac

If output appears, the tunnel is active and listening. If no output, the tunnel is not running. Re-run your ssh or autossh command to restore it.

At this point, your OpenClaw gateway is no longer reachable from the public internet. Verify this by opening a browser on a different network (your phone on mobile data works) and navigating to http://your-server-ip:18789. You should get a connection refused or timeout. That is the expected result.

Step 5: Make the tunnel persistent

A manual SSH tunnel dies when you close the terminal or when the SSH connection drops. For daily use, you want the tunnel to come back automatically. There are two practical options: an SSH config shortcut, and autossh for automatic reconnection.

SSH config shortcut (recommended starting point)

An SSH config file is a plain text file on your local machine that stores SSH connection settings. Instead of typing the full tunnel command every time, you create a named entry and just run ssh tunnel-openclaw.

Write me an SSH config entry for my local machine that creates a tunnel to your gateway. My server’s IP is [your-server-ip] and my SSH username is [your-username]. Save it to the right place on my machine.

Manual path: On Mac/Linux, open or create ~/.ssh/config (full option reference at man.openbsd.org/ssh_config). On Windows, the file is at C:\Users\your-username\.ssh\config. Add:

Host tunnel-openclaw
  HostName your-server-ip
  User your-username
  LocalForward 18789 localhost:18789
  ServerAliveInterval 60
  ServerAliveCountMax 3

Save the file. Then run ssh -N tunnel-openclaw to open the tunnel.

ServerAliveInterval 60 and ServerAliveCountMax 3 send a keepalive ping every 60 seconds. After 3 missed pings (3 minutes with no response), SSH closes the connection. If you are using autossh, it reopens the connection automatically.

autossh for automatic reconnection

autossh is a program that monitors an SSH tunnel and restarts it automatically if it drops. It is the production answer for a tunnel that needs to stay up.

Is autossh installed on my local machine? If yes, give me the autossh command to create a persistent tunnel to your gateway. If no, give me the installation command for my operating system.

Manual path: install autossh

# Mac
brew install autossh

# Ubuntu/Debian
sudo apt install autossh

# Windows (WSL2 only)
sudo apt install autossh

Run the tunnel with autossh:

autossh -M 0 -L 18789:localhost:18789 -N -o "ServerAliveInterval 60" -o "ServerAliveCountMax 3" your-username@your-server-ip

-M 0 disables autossh’s built-in monitoring port (not needed with modern SSH keepalives). The tunnel will restart automatically on disconnect.

On Linux: systemd user service (alternative to autossh): If your local machine runs Linux with systemd (systemd is the background process manager built into most modern Linux distributions, including Ubuntu), you can create a user-level systemd service that starts the tunnel on login and restarts it on failure. Ask your agent: “Create a systemd user service file on my local machine that runs a persistent SSH tunnel to my server on port 18789. Show me the file content and where to put it.” systemd user services are started with systemctl --user enable and do not require root privileges.
On Windows (native, without WSL2): autossh is not available natively on Windows. Use the SSH config shortcut method instead. For a tunnel that survives reboots on Windows, add the ssh command to your Windows startup folder (shell:startup) or use a task in Task Scheduler.

Step 6: Add a firewall rule as a second layer (optional)

Binding to 127.0.0.1 is sufficient to keep OpenClaw off the public internet. A firewall rule blocking port 18789 adds a second layer of defense: if the bind address were ever accidentally changed back, the firewall would still block external access.

UFW is the firewall management tool built into Ubuntu. It controls which network connections your server accepts by configuring the underlying Linux firewall.

Add a UFW firewall rule on my server that denies incoming connections on port 18789 from all external addresses. Show me the exact commands before running them. Do not run them yet.

Manual path:

sudo ufw deny 18789/tcp
sudo ufw status

UFW is Ubuntu’s firewall manager. If UFW is not enabled on your server, enabling it requires a separate step. Ask your agent first: “Is UFW enabled on my server? If not, what do I need to check before enabling it?” Enabling UFW without checking existing rules can block your SSH connection (port 22). Always verify before enabling.

Troubleshooting: what to do when it does not work

Most tunnel problems fall into four categories: the tunnel is not running, the wrong port is being used, the bind change was not applied, or the gateway did not restart cleanly. Work through these in order before assuming something is broken in your setup.

The dashboard does not load at localhost:18789

First check whether the tunnel is running at all. The tunnel command runs in a terminal and keeps the terminal busy (it appears to hang). If that terminal was closed, the tunnel is gone. Re-open a terminal and run the ssh command again.

Is there anything currently listening on port 18789 on my local machine? Run the appropriate check for my operating system and tell me what you find.

Manual check:

lsof -i :18789          # Mac
ss -tlnp | grep 18789   # Linux
netstat -ano | findstr :18789   # Windows

If no output: the tunnel is not running. Start it.
If output shows an ssh process: the tunnel is running. The problem is elsewhere. Check the gateway status next.

The tunnel is running but the dashboard still does not load

The tunnel is running locally but something is wrong on the server side. Check whether the OpenClaw gateway is actually running:

What is the current status of the OpenClaw gateway? Is it running? What port is it listening on and what is the current value of gateway.bind?

Manual check (run on the server via SSH):

openclaw gateway status
ss -tlnp | grep 18789

If the gateway is not running: start it with openclaw gateway start.
If the gateway is running but ss shows it listening on 0.0.0.0:18789 instead of 127.0.0.1:18789: the config change was not applied or the gateway did not restart after the change. Apply the bind change again and restart.
If the gateway is running and ss shows 127.0.0.1:18789: the config is correct. The problem is in the tunnel itself. Check the port numbers match.

The SSH command exits immediately with an error

Common errors and what they mean:

“Connection refused”: SSH cannot reach the server at all. Check that your server is running and that you are using the correct IP address and SSH port. Try a plain ssh user@server-ip (without the tunnel flags) to confirm basic SSH access works.

“Permission denied (publickey)”: SSH cannot authenticate. Your key is not loaded, the wrong key is being used, or the key is not authorized on the server. Run ssh-add -l to see what keys are loaded in your agent. If none: run ssh-add ~/.ssh/your-key-name. If you use password authentication: add -o PreferredAuthentications=password to the ssh command.

“bind: Address already in use”: Something is already listening on port 18789 on your local machine. Kill the existing process (see the port-already-in-use FAQ entry above) or change the local port in the tunnel command.

“Warning: remote port forwarding failed”: Rare with local forwards, but if it appears: the remote side of the tunnel could not bind. This should not happen with a standard -L local forward. Check that you are using -L (local forward), not -R (remote forward).

The tunnel connects but disconnects every few minutes

The SSH connection is timing out due to network inactivity. This is the ServerAliveInterval problem. If the keepalive settings are not in place, the server or an intermediate network device closes idle connections.

Update my SSH config entry for tunnel-openclaw to include ServerAliveInterval 60 and ServerAliveCountMax 3 if they are not already there. Show me the updated entry.

Manual fix: Add these two lines to your Host tunnel-openclaw entry in ~/.ssh/config:

  ServerAliveInterval 60
  ServerAliveCountMax 3

If you are running the ssh command directly (not via the config file), add -o "ServerAliveInterval=60" -o "ServerAliveCountMax=3" to the command. If disconnects continue even with keepalives, switch to autossh, which handles reconnection rather than trying to prevent disconnection.

The tunnel works but stops working after my laptop wakes from sleep

Sleep and wake break SSH connections even with keepalive settings. The connection was valid when the laptop went to sleep; the server timed it out while the laptop was suspended; the laptop woke up with a dead connection that has not yet detected it is dead. The keepalive settings detect this within 3 minutes of waking (3 missed pings times 60 seconds), but there is a gap. The real fix is autossh: it detects the dead connection and opens a new one instead of waiting for the keepalive to time out. After switching to autossh, sleep-and-wake cycles no longer cause persistent tunnel failures.

Complete hardening guide

Brand New Claw

SSH tunneling is one layer. Brand New Claw covers the full security stack for a production OpenClaw deployment: bind address, firewall config, plugin vetting, credential rotation schedule, and the 15 config fields that leave you exposed by default. Drop it straight into your agent and it reads the guide and walks you through every change.

Get it for $37 →

FAQ

My agent is on the same machine as my browser. Do I still need the tunnel?

No. If OpenClaw is running on your laptop or desktop and you access it from the same machine, it is already only reachable locally by definition. The tunnel is only needed when your OpenClaw instance is on a remote server (a VPS in a data center) and you need to reach it from a different physical machine. That said, changing gateway.bind to 127.0.0.1 is still worth doing even on a local machine. It is a belt-and-suspenders measure: if your laptop is ever on a network where another device can reach your local ports (some corporate networks, some hotel Wi-Fi configurations), the loopback bind means your gateway is still not reachable. The bind change costs nothing and adds a layer. Do it regardless of whether you need the tunnel.

I changed gateway.bind to 127.0.0.1 and now I cannot reach OpenClaw at all. How do I recover?

This happens when the bind change was applied and the gateway restarted before the SSH tunnel was set up. The gateway is still running on the server. It is bound to loopback and not reachable from outside. To recover: SSH into your server directly (this works regardless of the bind setting, since SSH runs on port 22 and is separate from the OpenClaw gateway), then set up the SSH tunnel from that same SSH session using a local port forward. If you are already on the server via SSH, you can access the gateway at localhost:18789 directly in a terminal. If you need to restore the previous bind setting temporarily, SSH in and run: openclaw config set gateway.bind 0.0.0.0 and restart the gateway, then set up the tunnel before switching back. Do not leave the bind at 0.0.0.0 longer than necessary to set up the tunnel.

Can I access OpenClaw through the tunnel from a second device on my home network?

Not with the standard tunnel command. By default, ssh -L 18789:localhost:18789 binds the forwarded port to your machine’s loopback address, meaning only the machine running the ssh command can reach it. To make the tunnel endpoint accessible to other devices on your local network, change the bind address in the port forward: ssh -L 0.0.0.0:18789:localhost:18789 -N user@server. The 0.0.0.0 before the local port tells SSH to listen on all interfaces on your local machine, not just loopback. Other devices on the same network can then reach your OpenClaw at your machine’s local IP address on port 18789. This is appropriate on a trusted home network. Do not use 0.0.0.0 binding on public Wi-Fi, shared networks, or any network where you do not control what other devices are connected. On those networks, only loopback binding (the default) is safe.

I am getting “port already in use” when I try to start the tunnel. What is happening?

Something else is already listening on port 18789 on your local machine. The most likely cause is that a previous SSH tunnel is still running in the background. Check with lsof -i :18789 on Mac or ss -tlnp | grep 18789 on Linux to see what process is using the port. If it is a previous ssh or autossh process, kill it with the process ID shown in the output: kill [PID]. Then re-run your tunnel command. If the port is occupied by something unrelated to OpenClaw, change the local port in your tunnel command to something unused: ssh -L 19789:localhost:18789 -N user@server and access OpenClaw at localhost:19789 instead. The remote port (the second number, 18789) must still match your gateway port; only the local port (the first number) needs to change.

My SSH key has a passphrase. Do I have to enter it every time the tunnel reconnects?

Only if you are not using an SSH agent. An SSH agent is a background process that holds your decrypted private key in memory so you do not have to re-enter the passphrase every time SSH needs it. On Mac, the keychain handles this automatically after the first login. On Linux, start the agent with eval $(ssh-agent) and add your key with ssh-add ~/.ssh/your-key. On Windows with the native SSH client, the ssh-agent service handles it. Once your key is loaded into the agent, autossh and reconnecting tunnels use the cached key without prompting. If you are using autossh for automatic reconnection, the agent is required. autossh cannot prompt for a passphrase when it reconnects unattended in the background. Ask your agent: “How do I add my SSH key to the agent on my operating system so it does not ask for a passphrase on reconnect?”

Does the SSH tunnel encrypt my OpenClaw traffic?

Yes, completely. All traffic routed through an SSH tunnel is encrypted by the SSH connection. The encryption is applied at the SSH layer before any data leaves your local machine, and decrypted at the remote end inside the server before it reaches OpenClaw. From OpenClaw’s perspective, it is receiving a local connection from the same machine. From the network’s perspective, it sees only the SSH stream, which is encrypted with the negotiated cipher (AES-256-CTR or similar, depending on your SSH configuration). The content of your OpenClaw requests, responses, and any credentials in the payload are not visible to anyone on the network between you and the server. No additional TLS certificate setup is needed. The SSH encryption is sufficient for this use case.

What happens if the tunnel drops while I am in the middle of using OpenClaw?

OpenClaw’s gateway on the server continues running normally. The gateway has no awareness that your tunnel dropped. It is a local service waiting for connections. Your browser session to localhost:18789 will stop loading because the local port is no longer forwarded, but no work in progress is lost. When you restore the tunnel, OpenClaw picks up where it left off. If you are using autossh, the reconnection happens automatically, usually within a few seconds of the disconnect. Any browser tab open to localhost:18789 may need a manual refresh after the tunnel comes back. If you are on a session that was mid-task when the tunnel dropped, ask your agent what it was doing and whether the task completed. Long-running tasks that were in progress continue on the server side regardless of tunnel state.

I use OpenClaw’s Discord or Telegram integration. Does the bind change affect those?

No. Your messaging integrations are entirely unaffected by changes to gateway.bind. Discord, Telegram, Signal, and similar integrations work through outbound connections from your server to the external platform. Your OpenClaw instance initiates the connection to Discord’s API, not the other way around. The gateway.bind setting only controls who can make inbound connections to the OpenClaw gateway port. Since Discord and Telegram do not make inbound connections to that port, they are not touched by this change. The same applies to webhook-based integrations and the OpenClaw mobile companion app. The mobile app uses a separate pairing and communication protocol that does not go through the gateway port. All of these continue working normally after the bind change and gateway restart.

My server uses a non-standard SSH port. How do I adjust the tunnel command?

Use the -p flag in the ssh command to specify the server’s SSH port. For example, if your server’s SSH service runs on port 2222 instead of 22: ssh -L 18789:localhost:18789 -N -p 2222 user@your-server-ip. If you are using the SSH config file approach, add a Port line to your Host entry: Port 2222. The local port forward port (18789 on the left side) and the remote gateway port (18789 on the right side) are completely separate from the SSH service port. They stay the same. Only the SSH service port (where sshd listens) changes. Ask your agent: “What port is the SSH service running on my server?” if you are not sure.

What is the difference between SSH tunneling and a VPN for this purpose?

Both encrypt traffic between your machine and the server, but they operate differently. An SSH tunnel is a specific, narrow channel: it forwards one local port to one remote port, and nothing else passes through it. It requires no installation on the server beyond sshd (which is already running if you have SSH access), no additional software to configure, no certificates, and no network routing changes. A VPN creates a full network-level connection: all traffic from your machine routes through the server, not just the OpenClaw port. VPNs are more powerful but significantly more complex to set up and maintain. For the specific purpose of keeping OpenClaw off the public internet while still being able to access it remotely, SSH tunneling is the right tool. It is simpler, requires nothing extra on the server, and does exactly what is needed without the overhead of a full VPN. If you already have a VPN set up for other reasons, you can use it for OpenClaw access too, but you do not need one just for this.

Do I need to set up a new tunnel every time I reboot my computer?

With a manual ssh command: yes, the tunnel needs to be started after each reboot. With autossh added to your startup sequence: no. On Mac, you can add an autossh launch agent to run at login. Ask your agent to create a launchd plist for it. On Linux with systemd, a user-level systemd service (described in Step 5) handles this automatically. On Windows, the Task Scheduler method in Step 5 starts the ssh command at login. Once any of these persistence methods is in place, the tunnel comes back on its own after every reboot and reconnects automatically if the connection drops during normal use. The SSH config file alone does not provide persistence. It only makes the command shorter. Persistence requires autossh, systemd, launchd, or Task Scheduler.

I’m on a VPS with a cloud control panel (Hetzner, DigitalOcean, Linode). Do I need to change their firewall too?

Binding to 127.0.0.1 works regardless of what cloud firewalls are configured, because the process stops listening on the public interface entirely. No packet from the internet can reach a port that the process is not listening on. That said, adding a cloud firewall rule is a useful second layer. If the bind setting were ever accidentally changed back (after an update, a config restore, or a manual edit), the cloud firewall would still block external access to port 18789. Check your provider’s dashboard for a Firewall or Network section. Hetzner has a firewall tab directly on the server page. DigitalOcean has a Networking section with configurable inbound rules. Linode (Akamai) has Firewalls under the Network menu. Add a rule blocking all inbound traffic on port 18789 from any source. The SSH tunnel will still work because it goes through port 22, not 18789.

What port does OpenClaw use by default? How do I check if mine is different?

The default gateway port is 18789. This is set in your openclaw.json under gateway.port. If you or a previous setup changed it, the tunnel command needs to use the actual port, not 18789. Ask your agent: “What port is my OpenClaw gateway running on? Show me the gateway config block.” The agent reads the config and returns the exact value. Use that number in both places in the tunnel command: ssh -L [that-port]:localhost:[that-port] -N user@server. Both numbers must match because you are forwarding your local port to the same port on the remote server. If you want to use a different local port (for example, if 18789 is already in use on your local machine), only the first number changes: ssh -L 19789:localhost:18789 -N user@server. Then access OpenClaw at localhost:19789.


Go deeper

CVE-2026-25253: what it is, whether you’re exposed, and what to do now

The vulnerability that put 135,000 OpenClaw instances at risk. Whether you’re exposed and the exact steps to find out.

Read →

Someone is hitting my OpenClaw instance from outside my network

You’ve confirmed unauthorized access. Here’s the incident response sequence: what to do first, what to check, and how to lock it down.

Read →

I accidentally pushed my openclaw.json to GitHub

Every API key in your config just became public. The exact steps to rotate credentials, clean the history, and prevent it from happening again.

Read →