|
| 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