A Debian provisioning tool — privacy-first

A fresh Debian VPS,
hardened.

Three auditable shell scripts that provision and lock down a fresh Debian 13 box. No app. No telemetry. No lock-in. Built with privacy in mind — safe even for filming. Configuration is regenerated from the scripts on every run — nothing drifts between takes, nothing leaks on camera.

$ bash setup.sh --domain example.com --proxy-port 8000 vps-01
~/vps-setup — setup.sh bash
$ bash setup.sh --domain example.com vps-01
[1/5] verifying SSH connection
connected: vps-01
[2/5] waiting for cloud-init
status: done
[3/5] forcing DNS + sources + installing base deps
retry 1 in 2s: sudo apt update
base deps ready
[4/5] uploading bootstrap.sh + harden.sh to target:/root/
bootstrap.sh 6.4K
harden.sh 8.1K
[5/5] running bootstrap + harden on target
--- bootstrap ---
[1/3] installing packages
[2/3] scaffolding nginx
[3/3] bootstrap done
--- harden ---
[1/6] SSH daemon hardening
[2/6] fail2ban
[3/6] sysctl hardening + network tuning
cc: bbr / qdisc: fq
[4/6] unattended-upgrades
[5/6] UFW
[6/6] verification
=== VPS setup complete ===
Three scripts

Three files. Read them end to end.

01
setup.sh

Runs from your machine. Verifies SSH reachability, waits for cloud-init to finish, forces public DNS resolvers, swaps apt sources to a configurable mirror, and uploads the other two scripts. Then runs them in order. Prints no addresses — the target is supplied by the operator and never echoed.

Runs on your machine
02
bootstrap.sh

Runs on the VPS. Installs nginx, ufw, fail2ban, unattended-upgrades, python3, git, curl, wget, rsync, sqlite3. Generates an nginx security-headers snippet, a placeholder site (static or reverse-proxy to a port you specify), and a systemd unit template you fill in later. Creates /var/www/acme and carves /.well-known/acme-challenge/ out of the dotfile deny, so certificate issuance works out of the box.

Runs on the VPS
03
harden.sh

Runs on the VPS. SSH daemon hardening, ssh-audit cipher/KEX/MAC hardening, MOTD + LastLog suppression, fail2ban with an aggressive sshd jail and a recidive backstop, sysctl kernel hardening, BBR + fq network tuning, unattended-upgrades, and UFW rules.

Runs on the VPS
What it locks down

Everything that matters before the app goes on.

A fresh Debian box is not a hardened box. These are the layers this tool adds — every one of them a heredoc you can read.

SSH
Key-only auth. No passwords, no empty passwords. Max 3 attempts, no X11 forwarding, no agent forwarding, no TCP forwarding, 300s client-alive interval. Ciphers and KEX limited to the ssh-audit hardened set.
fail2ban
Aggressive sshd filter, three attempts = one hour ban. Incremental bans doubling up to a week. A separate recidive jail bans repeat offenders for a full week across all ports. Bans go through UFW.
UFW
Default deny inbound. Only 22, 80, 443 open. Port 22 is rate-limited to six connections per thirty seconds per IP. Nothing else gets through.
sysctl
Kernel hardening plus network tuning. SYN cookies, no source routing, no ICMP broadcast, martian logging. Then BBR + fq congestion control, TCP fast open, tightened buffers, MTU probing.
upgrades
unattended-upgrades enabled and scheduled. Security patches apply automatically. No manual rebuilds required after every CVE.
MOTD
Suppressed. LastLog suppressed. Less information leaked to unauthenticated sessions. Your shell environment is never touched.
The architecture rule

Config is regenerated from scripts.
Nothing is copied from a snapshot.

Every /etc/* file this tool writes is a heredoc in one of the three scripts. Re-read the scripts, you know exactly what's on the box. Re-run the tool, you get the same result. Nothing accumulates, nothing drifts, nothing fights the machine.

This is why re-running setup.sh on an already-provisioned box is a no-op apart from package upgrades, and why there's no state file to corrupt or migrate.

The same discipline applies to what ends up on camera. Every script carries a redact_ips() helper and a top-of-file note: the target is supplied, the address is never echoed. Between takes, nothing leaks — not in the terminal, not in the logs, not in the frame.