Skip to content

Commit 4c396ad

Browse files
davidtkeaneclaude
andcommitted
Add VPS security hardening + OpenClaw Docker setup blog post
Covers Hostinger VPS setup, Docker port mapping security, OpenClaw deployment on custom port with SSH tunnel, and the parseInt port parsing bug discovery. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 5edda00 commit 4c396ad

File tree

1 file changed

+286
-0
lines changed

1 file changed

+286
-0
lines changed
Lines changed: 286 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
1+
---
2+
title: "From Zero to Secure VPS: Docker, OpenClaw, and HellCoin in One Night"
3+
date: 2026-02-02 21:00:00 +0000
4+
categories: [Infrastructure, Security]
5+
tags: [vps, hostinger, docker, openclaw, security, ufw, ssh, hardening, solana, hellcoin, forgiveme-life, tor, cryptocurrency, devops, linux, ubuntu, tutorial]
6+
pin: false
7+
math: false
8+
mermaid: false
9+
---
10+
11+
## Overview
12+
13+
Tonight I went from a bare Hostinger KVM VPS to a fully secured server running OpenClaw in Docker -- accessible only through an SSH tunnel on a custom port. Along the way, I learned about Docker port mapping security, discovered a port-parsing bug in OpenClaw's CLI, and saved myself EUR30 per month by killing my AWS instance.
14+
15+
This post covers the full journey: VPS hardening, Docker installation, OpenClaw deployment, the mistakes I made, and the security decisions behind every choice.
16+
17+
---
18+
19+
## Who Am I?
20+
21+
My name is David Keane. I am a 51-year-old student pursuing my Masters in Cybersecurity at the University of Galway (via NCI Dublin). I am dyslexic, ADHD, and autistic -- diagnosed at 39 -- and I have spent 14 years turning those diagnoses into superpowers.
22+
23+
I am building [ForgivMe.life](https://forgiveme.life/) -- an anonymous confession website where visitors can "pay for their burdens" with HellCoin (H3LL), a Solana token I created. Tonight's VPS setup is part of a bigger infrastructure plan for my college AI class demo.
24+
25+
---
26+
27+
## The Problem
28+
29+
I had too many things running on expensive, insecure, or unreliable platforms:
30+
31+
- **AWS Free Tier** was charging me EUR30/month for a simple relay server
32+
- **Tor hidden service** was running on my Mac (dies when I close the lid)
33+
- **No server** for future bots, APIs, or always-on services
34+
- **OpenClaw** (AI agent platform) needed a secure home for Moltbook registration
35+
36+
I needed a single VPS that could handle everything, secured properly from day one.
37+
38+
---
39+
40+
## The Solution: Hostinger KVM 2 VPS
41+
42+
I purchased a Hostinger KVM 2 VPS for about EUR200 for 2 years. That works out to roughly EUR8.33 per month -- compared to the EUR30 per month AWS was bleeding from me.
43+
44+
**Specs:**
45+
- Ubuntu 24.04.3 LTS (Noble Numbat)
46+
- Kernel 6.8.0-90
47+
- Full root access
48+
- Docker capable
49+
50+
I also grabbed two free domains: **confesstoai.org** and **h3llcoin.cloud**.
51+
52+
---
53+
54+
## Step 1: Security Hardening (The Right Way)
55+
56+
### Change the Default Password
57+
58+
Hostinger gives you a random root password. First thing -- change it through hPanel. Never keep default credentials.
59+
60+
### Create a Non-Root User
61+
62+
```bash
63+
adduser --disabled-password --gecos "Ranger" ranger
64+
usermod -aG sudo ranger
65+
echo "ranger ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/ranger
66+
chmod 440 /etc/sudoers.d/ranger
67+
```
68+
69+
### Set Up SSH Key Authentication
70+
71+
From your local machine, copy your public key:
72+
73+
```bash
74+
mkdir -p ~/.ssh && chmod 700 ~/.ssh
75+
echo 'ssh-rsa YOUR_PUBLIC_KEY' >> ~/.ssh/authorized_keys
76+
chmod 600 ~/.ssh/authorized_keys
77+
```
78+
79+
Copy the same key to your non-root user's home directory.
80+
81+
### Lock Down SSH
82+
83+
Edit `/etc/ssh/sshd_config`:
84+
85+
```
86+
PermitRootLogin no
87+
PasswordAuthentication no
88+
```
89+
90+
Then restart SSH: `systemctl restart ssh`
91+
92+
**What I should have done:** Test the non-root user login BEFORE disabling root. If you lock yourself out, you need hPanel console access to fix it.
93+
94+
### Configure UFW Firewall
95+
96+
```bash
97+
ufw default deny incoming
98+
ufw default allow outgoing
99+
ufw allow 22/tcp # SSH
100+
ufw allow 80/tcp # HTTP
101+
ufw allow 443/tcp # HTTPS
102+
ufw enable
103+
```
104+
105+
Only three ports open. Everything else is blocked.
106+
107+
---
108+
109+
## Step 2: Install Docker and Node.js
110+
111+
Docker from the official repository (not the Ubuntu snap):
112+
113+
```bash
114+
# Add Docker GPG key and repo
115+
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.asc
116+
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) stable" | sudo tee /etc/apt/sources.list.d/docker.list
117+
sudo apt-get update && sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
118+
```
119+
120+
**Gotcha:** Hostinger had a pre-existing Docker GPG key at a different path. I got a "Conflicting values for Signed-By" error. Fix: remove the old key file and let your fresh install take over.
121+
122+
Node.js 22 LTS via NodeSource:
123+
124+
```bash
125+
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
126+
sudo apt-get install nodejs
127+
```
128+
129+
Result: Docker 29.2.1, Node.js 22.22.0, npm 10.9.4.
130+
131+
---
132+
133+
## Step 3: OpenClaw on a Custom Port (The Security Deep Dive)
134+
135+
This is where it gets interesting. OpenClaw is an AI agent platform that listens on port **18789** by default. The problem? Shodan bots actively scan for this port. If you expose it, you are found within hours.
136+
137+
### The Three-Layer Security Approach
138+
139+
**Layer 1: Custom Port Mapping**
140+
141+
OpenClaw is hardcoded to listen on 18789 inside its container. You cannot change that. But Docker can remap it:
142+
143+
```bash
144+
docker run -p 127.0.0.1:2404:18789 openclaw
145+
```
146+
147+
This maps the internal 18789 to external 2404. I chose 2404 -- my birth month and year. Easy to remember, impossible to guess.
148+
149+
**Layer 2: Loopback Binding**
150+
151+
The `127.0.0.1` prefix is critical. Without it, Docker defaults to `0.0.0.0` -- meaning the port is exposed to the entire internet. With it, only processes on the VPS itself can connect.
152+
153+
**Layer 3: SSH Tunnel**
154+
155+
To access OpenClaw from my Mac:
156+
157+
```bash
158+
ssh -4 -i ~/.ssh/mac_to_mac_rsa -L 2404:127.0.0.1:2404 ranger@76.13.37.73
159+
```
160+
161+
Then open `http://127.0.0.1:2404` in a browser. The traffic is encrypted through SSH. No port needs to be opened in the firewall.
162+
163+
### The Docker Bind Bug I Discovered
164+
165+
OpenClaw's `--bind` flag accepts keywords like `loopback`, `lan`, `auto` -- NOT IP addresses. I initially set `--bind 127.0.0.1` and got:
166+
167+
```
168+
Invalid --bind (use "loopback", "lan", "tailnet", "auto", or "custom")
169+
```
170+
171+
But here is the real trap: setting `--bind loopback` makes OpenClaw listen on `127.0.0.1` **inside the Docker container**. Docker's port forwarding connects to the container via its virtual network interface, NOT its loopback. So the traffic never arrives.
172+
173+
**Fix:** Use `--bind lan` inside the container (binds to `0.0.0.0` internally), and let Docker handle the security externally with `127.0.0.1:2404:18789`.
174+
175+
### The Port Parsing Bug
176+
177+
OpenClaw's CLI uses the `OPENCLAW_GATEWAY_PORT` environment variable. Docker Compose needs this set to `127.0.0.1:2404` for the port mapping. But OpenClaw's code does:
178+
179+
```javascript
180+
const parsed = Number.parseInt(envRaw, 10);
181+
```
182+
183+
`parseInt("127.0.0.1:2404")` returns `127`. So the CLI tries to connect to `ws://127.0.0.1:127` and fails silently.
184+
185+
**Fix:** When running CLI commands via `docker compose exec`, override the env var:
186+
187+
```bash
188+
docker compose exec -e OPENCLAW_GATEWAY_PORT=18789 openclaw-gateway node dist/index.js devices list
189+
```
190+
191+
### The Device Pairing Requirement
192+
193+
Even after connecting with the gateway token, OpenClaw requires device pairing. The browser sends a pairing request that must be approved from the CLI:
194+
195+
```bash
196+
docker compose exec -e OPENCLAW_GATEWAY_PORT=18789 openclaw-gateway node dist/index.js devices list
197+
# Shows pending request with UUID
198+
docker compose exec -e OPENCLAW_GATEWAY_PORT=18789 openclaw-gateway node dist/index.js devices approve <UUID>
199+
```
200+
201+
After approval, refresh the browser and you are in.
202+
203+
---
204+
205+
## Step 4: The Bigger Picture
206+
207+
This VPS is one piece of a larger infrastructure:
208+
209+
| Service | Platform | Status |
210+
|---------|----------|--------|
211+
| ForgivMe.life (website) | InMotion Hosting | Live |
212+
| HellCoin tip widget | wallet.js + Solana | Working |
213+
| Phantom + Solflare wallets | Browser extensions | Integrated |
214+
| OpenClaw AI agent | Hostinger VPS (Docker) | Running |
215+
| Tor hidden service | Currently on Mac | Moving to VPS |
216+
| RangerChat relay | AWS (stopped) | Migrating to VPS |
217+
| confesstoai.org | Hostinger | Pending |
218+
| H3LL auto-delivery bot | VPS | Pending |
219+
220+
The goal: demonstrate the entire ecosystem for my college AI class.
221+
222+
---
223+
224+
## Mistakes I Made
225+
226+
1. **Tried to SSH with special characters in the password non-interactively.** The Hostinger default password had `;` and `?` which break shell escaping. Should have used hPanel or SSH keys from the start.
227+
228+
2. **Set `--bind loopback` for Docker container.** Docker networking means the container's loopback is not reachable from the host's port mapping. Use `--bind lan` inside, `127.0.0.1` outside.
229+
230+
3. **Used `config.json` instead of `openclaw.json`.** OpenClaw reads its config from `~/.openclaw/openclaw.json`. I created `config.json` and wondered why nothing changed.
231+
232+
4. **Passed `127.0.0.1:2404` as `OPENCLAW_GATEWAY_PORT`.** JavaScript's `parseInt` happily parses `"127.0.0.1:2404"` as `127`. No error, just silent failure.
233+
234+
5. **Almost bought a second VPS for EUR200.** Hostinger offered a pre-installed OpenClaw VPS. My AI assistant talked me out of it -- Docker install takes 10 minutes and one VPS handles everything.
235+
236+
---
237+
238+
## What I Learned
239+
240+
- **Docker port mapping is your firewall.** The `127.0.0.1:` prefix is more important than any application-level bind setting.
241+
- **SSH tunnels replace VPNs for single-service access.** No additional software, no port exposure, encrypted by default.
242+
- **Always test your non-root user BEFORE disabling root login.** Otherwise you are locked out.
243+
- **`parseInt` in JavaScript is dangerous.** It silently parses partial strings. `parseInt("127.0.0.1:2404")` is `127`, not an error.
244+
- **Default ports are a security liability.** Shodan indexes them. Change them. Even on loopback, the habit matters.
245+
- **One VPS can replace multiple cloud services.** I was paying EUR30/month for AWS when EUR8/month covers everything.
246+
247+
---
248+
249+
## The Money Saved
250+
251+
| Before | After |
252+
|--------|-------|
253+
| AWS: EUR30/month | Stopped (EUR0) |
254+
| No VPS | Hostinger: EUR8.33/month (2yr) |
255+
| No domains | confesstoai.org + h3llcoin.cloud (free yr1) |
256+
| **Total: EUR360/year** | **Total: EUR100/year** |
257+
258+
That is EUR260 saved per year, with more capability.
259+
260+
---
261+
262+
## What is Next
263+
264+
- Move the Tor hidden service from my Mac to the VPS (always-on)
265+
- Migrate the RangerChat relay from AWS
266+
- Deploy ForgivMe.life v2 with the HellCoin tip widget
267+
- Register ForgiveMeBot on Moltbook (the AI social network)
268+
- Add Metaplex metadata to H3LL so it stops showing as "Unknown Token"
269+
- Build the H3LL auto-delivery bot
270+
- Demo everything for college
271+
272+
One foot in front of the other.
273+
274+
---
275+
276+
## Resources
277+
278+
- [OpenClaw GitHub](https://github.com/openclaw/openclaw)
279+
- [OpenClaw Docker Docs](https://docs.openclaw.ai/install/docker)
280+
- [Docker Port Binding Docs](https://docs.docker.com/engine/network/#published-ports)
281+
- [UFW Essentials](https://www.digitalocean.com/community/tutorials/ufw-essentials-common-firewall-rules-and-commands)
282+
- [SSH Tunneling Explained](https://www.ssh.com/academy/ssh/tunneling)
283+
284+
---
285+
286+
*Written by David Keane -- Masters student, HellCoin creator, and someone who learned the hard way that `parseInt("127.0.0.1")` equals `127`.*

0 commit comments

Comments
 (0)