DEV Community

Cover image for Stop Hardening Your Server. Start Hiding It.
Dhruv malaviya
Dhruv malaviya

Posted on

Stop Hardening Your Server. Start Hiding It.

I used to be proud of my server hardening skills.

SSH keys, fail2ban, UFW, SELinux, read-only root, the whole thing. I could lock down a VPS in twenty minutes and sleep soundly.

Then I checked the auth logs on a fresh server and saw thousands of brute-force attempts in 24 hours. The server had been online for maybe six hours.

I wasn't being targeted. I was just visible.

That's when I started wondering: what if the best security isn't better hardening — it's not being findable in the first place?

The public IP problem
Every VPS gives you a public IPv4 address by default. It's convenient. It's also an invitation.

Bots scan the entire internet constantly. If you have SSH on port 22 and a public IP, you're getting hit. It's not personal. It's automatic.

I had done everything "right":

Key-based SSH only
fail2ban
UFW with minimal ports
Automatic updates
Non-root user
But the attempts never stopped. Because my server had an address.

My app didn't need one. My users hit a domain. My database shouldn't be reachable from outside. Admin access was just for me. The public IP was solving almost nothing while creating a permanent target.

Hiding the server instead
I moved the project to Krova Cloud, which gives you Firecracker microVMs called Cubes. The default: no public IP.

Your Cube sits on a private network. Web traffic comes through managed ingress with automatic HTTPS. SSH is only available through an explicit port mapping locked to your IP.

Creating one:

Bash

`npm i -g @krovacloud/cli

krova cubes create myapp \
--cpu 2 \
--ram 4 \
--disk 40 \
--image ubuntu-24.04

krova ssh myapp
root@myapp:~#`

No VPC. No security groups. No public IP.

If you prefer the API:

Bash

curl -X POST https://krova.cloud/api/v1/spaces/$SPACE/cubes \
-H "X-API-KEY: $KROVA_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"name": "myapp",
"image": "ubuntu-24.04",
"resources": { "vcpu": 2, "ramGb": 4, "diskGb": 40 },
"sshPublicKey": "ssh-ed25519 AAAA...",
"region": "eu-central"
}'
What changed

After 48 hours, I checked the auth logs.

text

`# Before (public VPS)
Failed password for root from 185.x.x.x
Failed password for root from 103.x.x.x
Failed password for root from 45.x.x.x
...thousands more

After (Krova Cube)

nothing`

No brute-forces. No port scans. No 2 AM anxiety about whether I left something exposed.

The server still works. Users still reach the API through the domain. HTTPS still terminates at Krova's ingress. But the machine itself doesn't exist on the public internet.

Admin access without a public IP

If you need SSH, you open a port mapping and lock it to your IP:

Bash

krova ports add myapp \
--protocol tcp \
--port 22 \
--allowed-ips 203.0.113.10/32

Only your IP can reach it. Everyone else sees nothing.

For web traffic, point your domain at Krova's ingress. They handle TLS and hide the origin. Your app doesn't need to know it's not directly exposed.

Bonus: real isolation
Krova Cubes are Firecracker microVMs, so each one boots its own kernel. Not shared with other tenants. Not shared with the host.

For a small side project, that's probably more isolation than necessary. But after reading one too many container escape write-ups, I prefer a real VM boundary for anything I care about.

text

VPS: public IP + shared kernel
Container: maybe public IP + definitely shared kernel
Cube: no public IP + own kernel

The takeaway
Hardening is good. Hiding is better.

A server without a public address can't be brute-forced. Can't be port-scanned. Can't be accidentally exposed by a bad firewall rule.

We've accepted public IPs as the default because they're familiar. But most apps don't need their machine to be reachable from the whole internet. They need a domain, TLS, and a way for the owner to admin things.

Everything else is attack surface.

Try it
If you're curious, Krova signup doesn't require a card and the first $5 top-up doubles to $10:

Bash

npm i -g @krovacloud/cli
krova signup
krova cubes create test --cpu 1 --ram 2 --disk 20 --image ubuntu-24.04
krova ssh test

Inside the Cube, run:

Bash

ss -tlnp
Nothing public. That's the whole point.

Am I wrong about public IPs? Is hardening still the better default? Tell me why , I genuinely want to hear the counterarguments.

Top comments (0)